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.tables with generated id values, position coordinates, and field arrays
  • Columns map to field objects capturing name, type, default, nullable, unique, primary flags
  • Enums populate database.enums with 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 name within matched tables
  • Enums match by name globally

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:

  1. Re-render of the diagram canvas with new/updated tables
  2. Synchronization of the side panel's schema browser
  3. Invalidation of derived views (SQL export, share links)
  4. 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 ref statements 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: fromDBML uses @dbml/core to 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: toDBML ensures 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →