Skip to content

saleyn/dbml

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

52 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DBML — Elixir Parser and Code Generator

build Hex.pm Hex.pm

A complete Elixir implementation for parsing Database Markup Language (DBML) schemas and generating Ecto schema files and Ecto migrations automatically.

Features:

  • Full DBML syntax parser using NimbleParsec
  • Generate Ecto schema files from DBML definitions
  • Generate Ecto migration files with intelligent change detection
  • Generate DBML from existing Ecto schema files (reverse operation)
  • Incremental updates: only regenerate changed schemas and migrations
  • Type mapping, relationships (belongs_to), enums, indexes, constraints
  • Zero-friction integration with Phoenix/Ecto projects
  • CLI tools: Standalone dbml escript and Mix tasks for command-line usage

Table of Contents


Installation

Add dbml to your dependencies in mix.exs:

def deps do
  [
    {:dbml, "~> 0.3"}
  ]
end

Then run:

mix deps.get

Quick Start

Parse a DBML file

{:ok, tokens} = DBML.parse_file("schema.dbml")

Generate Ecto schemas

{:ok, tokens} = DBML.parse_file("schema.dbml")

DBML.generate_ecto_schemas(tokens, "lib/my_app/schema", namespace: "MyApp.Schema")
# Output: lib/my_app/schema/users.ex, lib/my_app/schema/posts.ex, ...

Generate Ecto migrations

DBML.generate_ecto_migrations(
  tokens,
  "priv/repo/migrations",
  "MyApp.Repo"
)
# Output: priv/repo/migrations/20000101000001_create_users.exs, ...

CLI Usage (Escript & Mix Tasks)

The DBML library includes both a standalone escript binary and Mix tasks for convenient command-line usage.

Building the escript

mix escript.build
# Produces: ./dbml (standalone executable)

Using the dbml escript

# Generate Ecto schemas from DBML
./dbml schemas schema.dbml -o lib/my_app/schema --namespace MyApp.Schema

# Generate Ecto migrations from DBML
./dbml migrations schema.dbml -o priv/repo/migrations -r MyApp.Repo

# Generate DBML from existing Ecto schemas
./dbml file lib/my_app/schema -o schema.dbml --project-name MyApp

# Show help
./dbml help
./dbml schemas --help
./dbml migrations --help
./dbml file --help

Using Mix tasks

The same functionality is available as Mix tasks for use during development:

# Generate Ecto schemas from DBML
mix dbml.schemas schema.dbml -o lib/my_app/schema --namespace MyApp.Schema

# Generate Ecto migrations from DBML
mix dbml.migrations schema.dbml -o priv/repo/migrations -r MyApp.Repo

# Generate DBML from existing Ecto schemas
mix dbml.file lib/my_app/schema -o schema.dbml --project-name MyApp

# Show help for each task
mix help dbml.schemas
mix help dbml.migrations
mix help dbml.file

Escript options

dbml schemas <DBML_FILE> [OPTIONS]

Generate Ecto schema files from a DBML schema.

-o, --output-dir DIR         Output directory for .ex files (required)
-n, --namespace MODULE       Module namespace prefix (optional)
--singularize true|false     Singularize table names (default: true)
--update true|false          Allow overwriting existing files (default: false)

dbml migrations <DBML_FILE> [OPTIONS]

Generate Ecto migration files from a DBML schema.

-o, --output-dir DIR         Output directory for migration files (required)
-r, --repo MODULE            Repo module name (required)
--base-timestamp TIMESTAMP   Base timestamp for migrations (default: 20000101000000)
--update true|false          Allow incremental updates (default: false)

dbml file <SCHEMAS_DIR> [OPTIONS]

Generate a DBML file from existing Ecto schema files (reverse operation).

-o, --output FILE            Output path for the .dbml file (required)
--project-name NAME          Project name for DBML header (optional)
--database-type TYPE         Database type (default: PostgreSQL)

Parsing DBML

Parse from string

dbml_string = """
table users {
  id int [pk]
  email varchar [unique, not null]
  created_at timestamp
}
"""

{:ok, tokens} = DBML.parse(dbml_string)

Parse from file

{:ok, tokens} = DBML.parse_file("schema.dbml")

Error handling

case DBML.parse(invalid_dbml) do
  {:ok, tokens} -> process_tokens(tokens)
  {:error, reason} -> handle_error(reason)
end

Generating Ecto Schemas

Basic usage

{:ok, tokens} = DBML.parse_file("schema.dbml")

# Generate with default namespace (extracted from DBML project name, or empty)
{:ok, paths} = DBML.generate_ecto_schemas(tokens, "lib/my_app/schema")

# Generate with explicit namespace
{:ok, paths} = DBML.generate_ecto_schemas(
  tokens,
  "lib/my_app/schema",
  namespace: "MyApp.Schema"
)

Options

:namespace (string, optional)

Module namespace prefix for generated schemas.

Default: Extracted from DBML project { name: "..." }, or empty string "".

{:ok, paths} = DBML.generate_ecto_schemas(
  tokens,
  "lib/my_app/schema",
  namespace: "MyApp.Schema"
)
# Generates: MyApp.Schema.User, MyApp.Schema.Post, etc.

:singularize (boolean, default: true)

Convert plural table names to singular module names.

# With singularize: true (default)
# Table: users → Module: User
# Table: posts → Module: Post

# With singularize: false
{:ok, paths} = DBML.generate_ecto_schemas(
  tokens,
  "lib/my_app/schema",
  singularize: false
)
# Table: users → Module: Users
# Table: posts → Module: Posts

:update (boolean, default: false)

Control behavior when schema files already exist.

# update: false (default) — error if any file exists
{:error, "File already exists: lib/my_app/schema/users.ex"} =
  DBML.generate_ecto_schemas(tokens, "lib/my_app/schema")

# update: true — overwrite existing files
{:ok, paths} = DBML.generate_ecto_schemas(
  tokens,
  "lib/my_app/schema",
  update: true
)

Return value

  • Success: {:ok, ["/path/to/users.ex", "/path/to/posts.ex", ...]}
  • Error: {:error, "File already exists: /path/to/file.ex"} (when update: false)

Generated schema features

The generator creates full Ecto schemas with:

  • Primary keys: standard id, custom PKs, composite PKs, autoincrement
  • Relationships: belongs_to for foreign keys, alias resolution
  • Timestamps: automatic timestamps() when both created_at and updated_at present
  • Types: full mapping of DBML types to Ecto types
  • Enums: Ecto.Enum fields for DBML enums
  • Constraints: null: false, unique: true in field definitions

Generating Ecto Migrations

Basic usage

{:ok, tokens} = DBML.parse_file("schema.dbml")

# Generate migrations
{:ok, paths} = DBML.generate_ecto_migrations(
  tokens,
  "priv/repo/migrations",
  "MyApp.Repo"
)

Options

:base_timestamp (integer, default: 20000101000000)

Base timestamp for migration file naming. Each table gets base + index.

{:ok, paths} = DBML.generate_ecto_migrations(
  tokens,
  "priv/repo/migrations",
  "MyApp.Repo",
  base_timestamp: 20_250_101_000_000
)
# Generates: 20250101000001_create_users.exs, 20250101000002_create_posts.exs, ...

:update (boolean, default: false)

Control incremental migration generation when schemas change.

update: false (default):

  • Returns error if any migration file exists
  • No files are written
{:error, "File already exists: priv/repo/migrations/20000101000001_create_users.exs"} =
  DBML.generate_ecto_migrations(tokens, "priv/repo/migrations", "MyApp.Repo")

update: true:

  • Detects existing migration files
  • Compares generated content with existing migrations
  • Unchanged tables: skipped (no new file)
  • Changed tables: creates new migration with fresh timestamp (old file untouched)
  • New tables: creates migration at appropriate timestamp
# First generation
{:ok, ["/priv/repo/migrations/20000101000001_create_users.exs"]} =
  DBML.generate_ecto_migrations(tokens, "priv/repo/migrations", "MyApp.Repo", update: true)

# Schema updated: add email column to users, add posts table
{:ok, paths} =
  DBML.generate_ecto_migrations(new_tokens, "priv/repo/migrations", "MyApp.Repo", update: true)

# Returns: [
#   "/priv/repo/migrations/20000101000002_create_users.exs",    # changed (new migration)
#   "/priv/repo/migrations/20000101000002_create_posts.exs"     # new table
# ]

# Old migration still exists:
# /priv/repo/migrations/20000101000001_create_users.exs (unchanged)

Return value

  • Success: {:ok, ["/path/to/20000101000001_create_users.exs", ...]}
  • Error: {:error, "File already exists: /path/to/file.exs"} (when update: false)

Generated migration features

The generator creates Ecto migrations with:

  • Table creation: create table(...) with correct options
  • Columns: add :col, :type, opts with proper type mapping
  • Primary keys: standard id (skipped), custom PKs, composite PKs
  • Foreign keys: references(:table, column: :col) with automatic indexes
  • Timestamps: timestamps() for created_at/updated_at
  • Constraints: null: false, unique: true, default values
  • Indexes: explicit indexes from DBML, automatic FK indexes
  • Ordering: topological sort ensures referenced tables are created first

Migration versioning workflow

Migrations are append-only and audit-preserving:

# Day 1: Create initial schema
schema_v1 = """
table users {
  id int [pk]
  name varchar
}
"""

{:ok, tokens_v1} = DBML.parse(schema_v1)
{:ok, [users_mig]} =
  DBML.generate_ecto_migrations(tokens_v1, "priv/repo/migrations", "MyApp.Repo")
# Creates: 20000101000001_create_users.exs

# Day 2: Add column to users, add posts table
schema_v2 = """
table users {
  id int [pk]
  name varchar
  email varchar
}

table posts {
  id int [pk]
  user_id int [ref: > users.id]
  title varchar
}
"""

{:ok, tokens_v2} = DBML.parse(schema_v2)
{:ok, new_migs} =
  DBML.generate_ecto_migrations(tokens_v2, "priv/repo/migrations", "MyApp.Repo", update: true)
# Creates: 20000101000002_create_users.exs (users changed, new migration)
#          20000101000002_create_posts.exs (posts is new)
# Keeps:   20000101000001_create_users.exs (original, unchanged)

# Database migration history preserved:
# → 20000101000001_create_users.exs
# → 20000101000002_create_users.exs  (alter users table)
# → 20000101000002_create_posts.exs  (create posts table)

Generating DBML from Ecto Schemas

Generate a DBML schema file from existing Ecto schema files. This is the reverse operation: instead of parsing DBML and generating schemas, read Ecto schemas and produce DBML.

Basic usage

# Generate DBML from all .ex files in a directory
{:ok, output_path} = DBML.schemas_to_dbml(
  "lib/my_app/schema",
  "schema.dbml"
)

With options

{:ok, output_path} = DBML.schemas_to_dbml(
  "lib/my_app/schema",
  "schema.dbml",
  project_name: "MyApp",
  database_type: "PostgreSQL"
)

Options

:project_name (string, optional)

Adds a project block to the generated DBML with metadata.

{:ok, output_path} = DBML.schemas_to_dbml(
  "lib/my_app/schema",
  "schema.dbml",
  project_name: "MyApp"
)

# Generates:
# project MyApp {
#   database_type: "PostgreSQL"
# }

:database_type (string, default: "PostgreSQL")

Sets the database type in the project block.

{:ok, output_path} = DBML.schemas_to_dbml(
  "lib/my_app/schema",
  "schema.dbml",
  project_name: "MyApp",
  database_type: "MySQL"
)

Supported Ecto features

The generator reads Ecto schemas and extracts:

  • Primary keys: standard id int [pk], custom PKs with @primary_key, autoincrement
  • No primary key: @primary_key false
  • Field types: full mapping of Ecto types to DBML (integer, string, boolean, float, decimal, date, datetime, uuid, jsonb, etc.)
  • Field constraints: null: false, unique: true, default values
  • Ecto.Enum fields: extracted with all enum values
  • Timestamps: created_at and updated_at columns
  • Associations: belongs_to generates foreign key columns and ref: statements
  • Custom foreign keys: respects foreign_key: options in associations
  • Type back-mapping: Ecto atom types (:integer, :string, etc.) → DBML strings ("int", "varchar", etc.)

Example

Input schema files:

# lib/my_app/schema/user.ex
defmodule MyApp.Schema.User do
  use Ecto.Schema

  schema "users" do
    field :email, :string, null: false, unique: true
    field :status, Ecto.Enum, values: [:active, :inactive, :pending]
    timestamps()
  end
end

# lib/my_app/schema/post.ex
defmodule MyApp.Schema.Post do
  use Ecto.Schema

  schema "posts" do
    field :title, :string, null: false
    field :content, :string
    belongs_to :user, MyApp.Schema.User
    timestamps()
  end
end

Generation:

{:ok, path} = DBML.schemas_to_dbml(
  "lib/my_app/schema",
  "schema.dbml",
  project_name: "MyApp"
)

Output DBML:

project MyApp {
  database_type: "PostgreSQL"
}

enum status {
  active
  inactive
  pending
}

table users {
  id int [pk]
  email varchar [not null, unique]
  status status
  created_at timestamp
  updated_at timestamp
}

table posts {
  id int [pk]
  title varchar [not null]
  content varchar
  user_id int [ref: > users.id]
  created_at timestamp
  updated_at timestamp
}

ref: posts.user_id > users.id

Return value

  • Success: {:ok, output_path} — Path to the written .dbml file
  • Error: {:error, reason} — File I/O or parsing error

Use cases

Visualize existing schemas: Generate DBML from production Ecto schemas and visualize them in dbdiagram.io.

Document schemas: Keep a DBML representation of your database schema in version control.

Migrate between tools: Convert Ecto schemas to DBML, then use DBML tools for schema design and visualization.

Round-trip testing: Parse DBML → generate schemas → read schemas → generate DBML to verify round-trip fidelity.


Type Mapping

DBML to Ecto Type Mapping

DBML Type Ecto Type Notes
int, integer, smallint, bigint :integer
varchar, char, text, character varying :string
boolean, bool :boolean
float, double, real :float
decimal, numeric :decimal
date :date
datetime, timestamp, timestamptz :datetime
time :time
uuid :uuid Binary UUID type
json, jsonb :map
serial, bigserial :integer Auto-increment in migrations

Enum mapping

DBML enums are converted to Ecto.Enum fields:

enum product_status {
  out_of_stock
  in_stock
  running_low
}

table products {
  id int [pk]
  status product_status
}

Generates:

defmodule MyApp.Product do
  use Ecto.Schema

  schema "products" do
    field :status, Ecto.Enum, values: [:out_of_stock, :in_stock, :running_low]
  end
end

Examples

Complete schema example

schema.dbml:

project CMS {
  database_type: "PostgreSQL"
  note: "Content management system"
}

enum post_status {
  draft
  published
  archived
}

table users {
  id int [pk, increment]
  email varchar [unique, not null]
  full_name varchar
  created_at timestamp
  updated_at timestamp
}

table posts {
  id int [pk, increment]
  user_id int [ref: > users.id, not null]
  title varchar [not null]
  content text
  status post_status [default: 'draft']
  published_at datetime
  created_at timestamp
  updated_at timestamp

  indexes {
    (user_id, status)
    published_at [type: btree]
  }
}

table comments {
  id int [pk, increment]
  post_id int [ref: > posts.id, not null]
  user_id int [ref: > users.id, not null]
  content text [not null]
  created_at timestamp
  updated_at timestamp
}

Generate schemas:

{:ok, tokens} = DBML.parse_file("schema.dbml")

{:ok, paths} = DBML.generate_ecto_schemas(
  tokens,
  "lib/cms/schema",
  namespace: "CMS.Schema"
)

# Creates:
# - lib/cms/schema/users.ex
# - lib/cms/schema/posts.ex
# - lib/cms/schema/comments.ex

Generate migrations:

{:ok, paths} = DBML.generate_ecto_migrations(
  tokens,
  "priv/repo/migrations",
  "CMS.Repo"
)

# Creates:
# - priv/repo/migrations/20000101000001_create_users.exs
# - priv/repo/migrations/20000101000002_create_posts.exs
# - priv/repo/migrations/20000101000003_create_comments.exs
# (topological order: users first, then posts, then comments)

Handling schema evolution

# Initial schema
initial_schema = DBML.parse_file("schema.dbml")

# Generate all artifacts
DBML.generate_ecto_schemas(
  initial_schema,
  "lib/my_app/schema",
  namespace: "MyApp",
  update: false  # fail if files exist
)

DBML.generate_ecto_migrations(
  initial_schema,
  "priv/repo/migrations",
  "MyApp.Repo",
  update: false  # fail if files exist
)

# ... run migrations, commit ...

# Later: schema evolves
evolved_schema = DBML.parse_file("schema.dbml")

# Update schemas safely (only changed files rewritten)
DBML.generate_ecto_schemas(
  evolved_schema,
  "lib/my_app/schema",
  namespace: "MyApp",
  update: true  # overwrite changed, skip unchanged
)

# Update migrations intelligently (append-only)
DBML.generate_ecto_migrations(
  evolved_schema,
  "priv/repo/migrations",
  "MyApp.Repo",
  update: true  # new files for changed tables, skip unchanged
)

API Reference

DBML module

parse(dbml_string) :: {:ok, tokens} | {:error, reason}

Parse a DBML schema from a string.

Parameters:

  • dbml_string (string) — DBML schema definition

Returns:

  • {:ok, tokens} — Parsed tokens (keyword list)
  • {:error, reason} — Parse error details
{:ok, tokens} = DBML.parse("""
  table users {
    id int [pk]
  }
""")

parse_file(filepath) :: {:ok, tokens} | {:error, reason}

Parse a DBML schema from a file.

Parameters:

  • filepath (string) — Path to .dbml file

Returns:

  • {:ok, tokens} — Parsed tokens
  • {:error, reason} — File read or parse error
{:ok, tokens} = DBML.parse_file("priv/schema.dbml")

generate_ecto_schemas(tokens, output_dir, opts) :: {:ok, paths} | {:error, reason}

Generate Ecto schema files from parsed DBML tokens.

Parameters:

  • tokens (keyword list) — From parse/1 or parse_file/1
  • output_dir (string) — Directory to write .ex files
  • opts (keyword list, optional) — Generation options

Options:

  • :namespace (string) — Module namespace prefix
  • :singularize (boolean, default: true) — Singularize table names
  • :update (boolean, default: false) — Allow overwriting existing files

Returns:

  • {:ok, [paths]} — List of written file paths
  • {:error, "File already exists: ..."} — When update: false and file exists
{:ok, paths} = DBML.generate_ecto_schemas(
  tokens,
  "lib/my_app/schema",
  namespace: "MyApp.Schema",
  singularize: true,
  update: false
)

generate_ecto_migrations(tokens, output_dir, repo_module, opts) :: {:ok, paths} | {:error, reason}

Generate Ecto migration files from parsed DBML tokens.

Parameters:

  • tokens (keyword list) — From parse/1 or parse_file/1
  • output_dir (string) — Directory to write .exs migration files
  • repo_module (string) — Repo module name (e.g., "MyApp.Repo")
  • opts (keyword list, optional) — Generation options

Options:

  • :base_timestamp (integer, default: 20000101000000) — Migration timestamp base
  • :update (boolean, default: false) — Incremental generation

Returns:

  • {:ok, [paths]} — List of written migration file paths
  • {:error, "File already exists: ..."} — When update: false and file exists
{:ok, paths} = DBML.generate_ecto_migrations(
  tokens,
  "priv/repo/migrations",
  "MyApp.Repo",
  base_timestamp: 20_250_101_000_000,
  update: true
)

schemas_to_dbml(input_dir, output_path, opts) :: {:ok, output_path} | {:error, reason}

Generate a DBML schema file from existing Ecto schema files.

Reads all *.ex files in input_dir that contain use Ecto.Schema, parses their structure, and writes a single .dbml file to output_path.

Parameters:

  • input_dir (string) — Directory containing Ecto schema .ex files
  • output_path (string) — Path to write the generated .dbml file
  • opts (keyword list, optional) — Generation options

Options:

  • :project_name (string, optional) — Name for the DBML project block
  • :database_type (string, default: "PostgreSQL") — Database type for project block

Returns:

  • {:ok, output_path} — Path to the written .dbml file
  • {:error, reason} — File I/O or parsing error
{:ok, path} = DBML.schemas_to_dbml(
  "lib/my_app/schema",
  "schema.dbml",
  project_name: "MyApp",
  database_type: "PostgreSQL"
)

DBML Syntax Reference

For complete DBML syntax documentation, see the official DBML Documentation.

Quick reference

project ProjectName {
  database_type: "PostgreSQL"
  note: "Project description"
}

enum status_enum {
  active
  inactive
  pending
}

table users as U {
  id int [pk, increment]
  email varchar [unique, not null]
  name varchar
  status status_enum [default: 'active']
  created_at timestamp
  updated_at timestamp

  note: "User accounts table"

  indexes {
    email [unique]
    (status, created_at)
  }
}

table posts {
  id int [pk, increment]
  user_id int [ref: > U.id, not null]
  title varchar [not null]
  content text

  indexes {
    user_id
  }
}

ref: users.id < posts.user_id [comment: "One user has many posts"]

Common Use Cases

For a new Phoenix project (using CLI)

# 1. Create your DBML schema
cat > priv/schema.dbml << 'EOF'
table users {
  id int [pk]
  email varchar [unique, not null]
  created_at timestamp
  updated_at timestamp
}
EOF

# 2. Build the escript (or use mix tasks)
mix escript.build

# 3. Generate migrations
./dbml migrations priv/schema.dbml -o priv/repo/migrations -r MyApp.Repo

# 4. Generate schemas
./dbml schemas priv/schema.dbml -o lib/my_app/schema --namespace MyApp

# 5. Run migrations
mix ecto.migrate

Alternatively, use Mix tasks:

mix dbml.migrations priv/schema.dbml -o priv/repo/migrations -r MyApp.Repo
mix dbml.schemas priv/schema.dbml -o lib/my_app/schema --namespace MyApp
mix ecto.migrate

For a new Phoenix project (using Elixir)

# 1. Create your DBML schema (same as above)

# 2. In iex:
iex> {:ok, tokens} = DBML.parse_file("priv/schema.dbml")
iex> DBML.generate_ecto_migrations(tokens, "priv/repo/migrations", "MyApp.Repo")
iex> DBML.generate_ecto_schemas(tokens, "lib/my_app/schema", namespace: "MyApp")

# 3. Run migrations
mix ecto.migrate

For an existing project (using CLI)

# 1. Edit your DBML schema
# vim priv/schema.dbml

# 2. Regenerate using the escript (or mix tasks)
./dbml schemas priv/schema.dbml -o lib/my_app/schema --namespace MyApp --update true
./dbml migrations priv/schema.dbml -o priv/repo/migrations -r MyApp.Repo --update true

# 3. Review and run new migrations
mix ecto.migrate

Using Mix tasks:

mix dbml.schemas priv/schema.dbml -o lib/my_app/schema --namespace MyApp --update true
mix dbml.migrations priv/schema.dbml -o priv/repo/migrations -r MyApp.Repo --update true
mix ecto.migrate

For an existing project (using Elixir)

# Update DBML, then regenerate:
{:ok, tokens} = DBML.parse_file("priv/schema.dbml")

DBML.generate_ecto_schemas(
  tokens,
  "lib/my_app/schema",
  namespace: "MyApp",
  update: true  # overwrite changed, skip unchanged
)

DBML.generate_ecto_migrations(
  tokens,
  "priv/repo/migrations",
  "MyApp.Repo",
  update: true  # create new migrations for changed tables
)

# Review and run new migrations
mix ecto.migrate

For visualizing existing schemas (using CLI)

# 1. Generate DBML from your existing Ecto schemas
./dbml file lib/my_app/schema -o priv/schema.dbml --project-name MyApp

# 2. View it online at https://dbml.dbdiagram.io/home
# Copy the contents of priv/schema.dbml and paste it into the editor

Or using Mix task:

mix dbml.file lib/my_app/schema -o priv/schema.dbml --project-name MyApp

For visualizing existing schemas (using Elixir)

# Generate DBML from your existing Ecto schemas
{:ok, path} = DBML.schemas_to_dbml("lib/my_app/schema", "priv/schema.dbml", project_name: "MyApp")

# View it online at https://dbml.dbdiagram.io/home
# Copy the contents of priv/schema.dbml and paste it into the editor

Migrating from Ecto schemas to DBML-first workflow (using CLI)

# 1. Visualize current schemas
./dbml file lib/my_app/schema -o schema.dbml --project-name MyApp

# 2. Use the DBML as the source of truth going forward
# Edit schema.dbml with new changes

# 3. Generate schemas and migrations from DBML
./dbml schemas schema.dbml -o lib/my_app/schema --namespace MyApp --update true
./dbml migrations schema.dbml -o priv/repo/migrations -r MyApp.Repo --update true

# 4. Run migrations
mix ecto.migrate

Or using Mix tasks:

mix dbml.file lib/my_app/schema -o schema.dbml --project-name MyApp
# Edit schema.dbml...
mix dbml.schemas schema.dbml -o lib/my_app/schema --namespace MyApp --update true
mix dbml.migrations schema.dbml -o priv/repo/migrations -r MyApp.Repo --update true
mix ecto.migrate

Migrating from Ecto schemas to DBML-first workflow (using Elixir)

# 1. Visualize current schemas
{:ok, _path} = DBML.schemas_to_dbml("lib/my_app/schema", "schema.dbml", project_name: "MyApp")

# 2. Use the DBML as the source of truth going forward
# Edit schema.dbml with new changes

# 3. Generate schemas and migrations from DBML
{:ok, tokens} = DBML.parse_file("schema.dbml")
DBML.generate_ecto_schemas(tokens, "lib/my_app/schema", namespace: "MyApp", update: true)
DBML.generate_ecto_migrations(tokens, "priv/repo/migrations", "MyApp.Repo", update: true)

# 4. Run migrations
mix ecto.migrate

Troubleshooting

"File already exists" error

When regenerating schemas or migrations, use update: true to allow overwrites:

# ❌ This fails if files exist
DBML.generate_ecto_schemas(tokens, "lib/my_app/schema")

# ✅ This overwrites changed files
DBML.generate_ecto_schemas(tokens, "lib/my_app/schema", update: true)

Migration ordering with foreign keys

The generator automatically orders migrations using topological sort to ensure referenced tables are created before tables that reference them. This respects table aliases in DBML.

Column/table names with spaces

Names with spaces are converted to snake_case in generated code:

table "user accounts" {
  "full name" varchar
}

Generates:

schema "user accounts" do
  field :full_name, :string
end

Contributing

Contributions are welcome! Please open an issue or pull request on GitHub.


License

Apache-2.0


References

About

Elixir parser for DBML (dbml.org)

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors