How DrawDB Parses and Reconciles DBML Files: A Deep Dive into the Import Pipeline
DrawDB parses DBML files using a two-stage pipeline that first converts DBML text to an AST via @dbml/core, then reconciles the resulting schema with the existing diagram by merging matching entities and surfacing conflicts for manual resolution.
The drawdb-io/drawdb project includes robust support for DBML (Database Markup Language), a readable DSL for describing database schemas. When users import DBML, the application must not only parse the syntax but also intelligently merge the incoming schema with any existing diagram state. This article examines the complete import pipeline from source file to rendered diagram.
The Entry Point: fromDBML in the Import Module
All DBML imports flow through src/utils/importFrom/dbml.js, which exports the core fromDBML function. This function accepts two parameters: the raw DBML string and the current database object representing the diagram's state.
// Conceptual signature based on drawDB architecture
export function fromDBML(dbmlString, database) {
// 1. Parse DBML to AST
// 2. Map AST to internal model
// 3. Reconcile with existing database
// 4. Return updated database
}
The function serves as the orchestrator, delegating parsing to the @dbml/core library and handling the complex merge logic internally.
Stage 1: Parsing DBML to an Abstract Syntax Tree
The DBML parsing step leverages the official @dbml/core package (or the standalone dbml-parser) to transform text into a structured AST. This AST contains four primary node types:
- Tables – with column definitions, indexes, and table-level notes
- Enums – named sets of allowed values
- Relationships – foreign key constraints between tables
- Notes – free-form documentation attached to tables or columns
The parser handles DBML's full syntax including:
- Data types with precision/scale (e.g.,
varchar(255),decimal(10,2)) - Column attributes:
pk,unique,not null,default,ref - Inline and block-style relationship definitions
- Multi-line notes and schema-level settings
If the DBML contains syntax errors, the parser throws descriptive exceptions that propagate up to the UI layer, surfacing line numbers and error messages in the code editor component.
Stage 2: Mapping the AST to DrawDB's Internal Model
Once parsing succeeds, drawDB walks the AST and constructs its own internal representation. This AST-to-model mapping preserves semantic meaning while translating to drawDB's native structure:
- Table nodes become objects in
database.tableswith generatedidvalues, position coordinates, and field arrays - Columns map to field objects capturing
name,type,default,nullable,unique,primaryflags - Enums populate
database.enumswith their value lists - Relationships transform into edge objects linking source table/field to target table/field, with cardinality (
-,<,>,<>)
The mapping process also extracts metadata like note properties and attaches them to the corresponding entities for display in the UI.
Stage 3: Reconciliation and Merge Logic
The most sophisticated aspect of drawDB's DBML import is its reconciliation engine. Rather than replacing the entire diagram, the system performs an intelligent merge:
Matching by Identity
- Tables match by
name(case-sensitive) - Columns match by
namewithin matched tables - Enums match by
nameglobally
Update Strategies
- Preservation of UI state: Existing table positions, colors, and manual layout adjustments are retained
- Field-level merges: When a column exists in both source and target, properties are updated individually; new columns are appended, removed columns are flagged
- Relationship rewiring: Foreign keys referencing matched tables are recreated; dangling references generate warnings
Conflict Detection The reconciler identifies problematic scenarios:
| Conflict Type | Resolution Behavior |
|---|---|
| Name collision on new table | Append numeric suffix, flag for review |
| Column type change | Update type, preserve notes/position |
| Primary key change | Update index structure, warn if data loss |
| Circular reference creation | Block and surface error dialog |
Conflicts are collected in a warnings array returned alongside the updated database, enabling the UI to present a reconciliation report before finalizing changes.
Stage 4: State Synchronization and Rendering
After reconciliation completes, the modified database object flows into drawDB's global state manager (implemented via React Context or similar). This triggers:
- Re-render of the diagram canvas with new/updated tables
- Synchronization of the side panel's schema browser
- Invalidation of derived views (SQL export, share links)
- Optional activation of auto-arrange (
src/utils/autoArrange.js) to position newly added tables without overlapping existing ones
The entire pipeline operates synchronously for typical schemas, keeping the UI responsive even for diagrams with dozens of tables.
Round-Trip Integrity: The Export Counterpart
DrawDB ensures bidirectional fidelity through toDBML in src/utils/exportAs/dbml.js. This function walks the internal model and emits DBML that will re-import to an equivalent diagram—preserving names, types, relationships, and notes. The export process:
- Orders tables topologically to respect foreign key dependencies
- Includes
refstatements for all relationships - Preserves custom attributes as DBML settings
- Generates human-readable formatting with consistent indentation
This symmetry allows workflows like: export → edit in external DBML editor → re-import → minimal diff in reconciliation.
Key Source Files
| Responsibility | Path |
|---|---|
| DBML import parser & reconciler | src/utils/importFrom/dbml.js |
| DBML export generator | src/utils/exportAs/dbml.js |
| Auto-arrangement utilities | src/utils/autoArrange.js |
| Supported format registry | src/data/constants.js |
| Code editor integration | src/components/CodeEditor/setUpDBML.js |
Summary
- Parse:
fromDBMLuses@dbml/coreto convert DBML text into a structured AST - Map: The AST walker translates DBML constructs into drawDB's native table, field, enum, and relationship objects
- Reconcile: Name-based matching preserves existing UI state while merging schema changes, with explicit conflict detection
- Sync: The updated database flows to React state, triggering canvas re-render and optional auto-arrangement
- Round-trip:
toDBMLensures exported DBML re-imports with minimal reconciliation churn
Frequently Asked Questions
What DBML syntax features does drawDB support?
DrawDB supports the complete DBML specification including table definitions, enums, relationships (ref), indexes, notes, and column attributes (pk, unique, not null, default). The parser handles both inline and block-style relationship declarations.
How does drawDB handle DBML import errors?
Syntax errors from @dbml/core propagate to the code editor component with line numbers and descriptive messages. The user must correct the DBML before import proceeds; partial or failed imports never modify the existing diagram.
Can I import DBML into a non-empty diagram without losing my layout?
Yes. The reconciliation engine matches existing tables by name and preserves their position coordinates, colors, and manual adjustments. Only schema-level changes (added/removed columns, type changes) are applied; visual properties remain intact.
What happens if my DBML defines a table that already exists with different columns?
The reconciler performs a field-level merge: matching columns by name update their properties, new columns are appended, and removed columns are flagged in the warnings report. The table's identity and position are preserved throughout.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →