User guide

Db2 and Teradata

Use --dialect db2 or --dialect teradata when generating or importing DDL. Live Db2 imports use a catalogued database alias and the db2 client. Live Teradata imports use bteq with host, user, password, and database settings.

bluep keeps a database schema as a model in plain text files, generates DDL from it, reads existing databases back into it, compares it with a live database, writes migrations, checks it against naming standards and publishes documentation and diagrams. The model lives in a git repository like code.

Reviews

bluep review create records model-level changes between Git revisions. Comments can be anchored to model objects or changes, and bluep review check <revision> can enforce an approved review in CI. Object anchors use entity:<entity>, attribute:<entity>.<attribute>, relationship:<relationship>, table:<target>.<schema>.<table> and column:<target>.<schema>.<table>.<column>; change anchors use change:<change-id>.

Install

Build from source with a stable Rust toolchain:

cargo build --release --workspace

The binary is target/release/bluep. It has no runtime dependencies of its own; psql, sqlite3, mysql and git are used only by the commands that talk to a database or to a repository.

bluep --version

prints bluep 0.2.0.

A model directory

A model is a directory with a model.bluep file and, optionally, more *.bluep files (for example one per subject area). Create one:

bluep init shop --name shop

This writes shop/model.bluep with a model shop { ... } declaration. The file format is described in docs/format.md. A small complete model looks like this:

model mini {
  description "Smallest model with exactly one lint finding."
}

naming {
  entity {
    case snake
  }
}

entity OrderLine {
  attribute id : bigint not null
  attribute quantity : integer not null
  key primary pk_order_line (id)
}

area "Core" {
  entity OrderLine at 40 40
}

target pg : postgresql {
  schema app {
    table order_line {
      entity OrderLine
      column id bigint not null attribute id
      column quantity integer not null attribute quantity
      primary key pk_order_line (id)
    }
  }
}

The model has two layers. The logical layer (domain, enum, term, entity, relationship, area) describes the business data. The physical layer (target) describes one concrete database per target and maps every table and column back to its entity and attribute.

The everyday loop

  1. Edit the text (or use the browser editor, see below).

  2. bluep fmt <dir> rewrites every file in canonical form and prints the files it changed. Canonical form is deterministic, so two people editing the same model get small, mergeable diffs.

  3. bluep check <dir> parses and validates; exit code 1 and one line per issue when something is wrong, for example relationship 'order_customer': child attribute 'customer_id' does not exist in entity 'order'. Syntax errors are reported as <file>:<line>:<col>: <message>.

  4. bluep lint <dir> applies the naming standards and rules configured in the model. For the model above:

     model.bluep:11:8: warning [naming] entity 'OrderLine' violates case snake
    

    Warnings do not change the exit code; errors make it 1. --json prints the findings as JSON for CI and --level error hides warnings. Rule severities are configured in the model:

     lint {
       missing-primary-key error
       unindexed-foreign-key warn
     }
    
  5. bluep generate ddl <dir> --target pg prints the complete, correctly ordered CREATE script for the target; --out schema.sql writes it to a file. --dialect sqlite generates for a dialect the model has no target for, deriving tables from the entities.

  6. bluep generate docs <dir> --out site writes a static HTML data dictionary (index.html, one page per entity and subject area, a glossary) and SVG diagrams with an automatic layout. Open site/index.html in a browser; nothing is fetched from the network.

Working against a database

Use bluep drift <dir> --target pg --database shop to report differences between a live database and the desired target. Add --since-deploy to distinguish live changes from model changes made after the last deployment.

bluep diff <dir> --target pg --against-db --dialect postgresql --database shop --host 127.0.0.1 --port 5432 --user app reads the live catalog through psql and prints the structural differences between the model's target and the database:

+ table public.payment
- column public.order.notes
~ column public.customer.email: type varchar(255) -> varchar(320)
? rename public.order_line.qty -> public.order_line.quantity (same type and nullability)

+ is an object to create, - one to drop, ~ an altered object and ? a rename candidate that is treated as a drop plus an add unless --accept-renames is given. Exit code 1 means there are differences; --json prints them as an array of change objects. --against-dir <dir2> compares two model directories instead.

bluep migrate takes the same arguments and prints the ALTER script that brings the database to the model. Destructive changes (drops, type changes) are only emitted with --allow-destructive; otherwise the script contains a -- skipped destructive: ... comment for each. On SQLite the tool rebuilds a table when ALTER cannot express a change.

To start from an existing database or script:

bluep import db --dialect sqlite --database shop.db --out shop
bluep import ddl schema.sql --dialect postgresql --out shop

Add --report reverse-report.json to either form to save a structured fidelity report. Import always prints notes and unrepresented objects to stderr even when no report path is supplied. See reverse-fidelity.md for the stable JSON and text formats. bluep import dbml schema.dbml --out shop

Each writes a model directory with the physical target and a derived logical layer (an entity per table, a relationship per foreign key). For Snowflake, BigQuery and Databricks pass connection details with --option (connection=, project= and region=, catalog=); Redshift uses the PostgreSQL flags with port 5439.

Importing from SQL Server and Oracle

bluep import db --dialect sqlserver --database shop --host 127.0.0.1 --port 1433 --user sa --password '...' --out ./shop
bluep import db --dialect oracle --database 127.0.0.1:1521/FREEPDB1 --user shop --password '...' --out ./shop

SQL Server runs sqlcmd; Oracle runs sqlplus (or SQLcl sql). A password can also be supplied through BLUEP_DB_PASSWORD instead of --password. Oracle always requires --user and a password; without --schema the connecting user's own schema is imported.

Browser editor

bluep serve shop

prints serving shop at http://127.0.0.1:7878/ and runs until interrupted. The page lists areas, entities and relationships; you can edit descriptions, attributes, keys and relationships, create entities and areas, drag boxes on a diagram and sync an entity to a target. Every change is validated and written back to the text files in canonical form, so git diff shows exactly what changed. See docs/web-ui.md.

History

bluep history shop --from HEAD~1 --to HEAD

compares the model at two git revisions and reports model-level changes rather than text lines, for example + attribute payment.refund_at or ~ attribute customer.email: type text(255) -> text(320), followed by the physical changes per target.

Interchange

bluep export json shop writes the model as canonical JSON (see docs/json-format.md); bluep import json reads it back byte-identically.

Merging

Models merge at the model level: bluep merge <base_dir> <ours_dir> <theirs_dir> --out <dir> merges three model directories, and bluep merge-file is a git merge driver for single .bluep files, so two branches that edit different entities or attributes merge cleanly. Set it up once per repository:

git config merge.bluep.name "bluep model merge"
git config merge.bluep.driver "bluep merge-file %O %A %B"

and add *.bluep merge=bluep to .gitattributes. Conflicts are printed as CONFLICT <path>: ... lines and resolved in favour of your side until you edit them. See docs/merge.md for the rules.

notation idef1x (in the model block or an area block) draws diagrams in IDEF1X instead of crow's foot, and stereotype fact, stereotype dimension or stereotype bridge in an entity block marks its role in a star schema: diagrams colour it, the data dictionary groups by it and the lint rule dimensional-star checks it.

Reports

Mappings and lineage

Mappings declare how local target tables or columns are sourced. Use bluep lineage <dir> column:target.schema.table.column --upstream to trace a local selector. See Mappings and lineage for the format, validation and external-source rules.

bluep report renders text from the model through a small template language. Seven templates are built in; list them with:

bluep report --list

classification.html   html  matrix of classification levels with the entities and attributes at each level
columns.csv            csv   one row per physical column: target, schema, table, type, nullability, default, mapping, classifications
dictionary.md          md    data dictionary with one section per entity: attributes, primary and unique keys
entities.csv           csv   one row per entity: name, description, attribute count, stereotype, subject areas
glossary.md            md    glossary terms with definitions, aliases and the entities and attributes using them
lint.md                md    lint findings grouped by rule
relationships.md       md    relationships with columns, cardinalities and referential actions

Run a built-in template against a model directory:

bluep report devdata/models/commerce --template entities.csv

name,description,attributes,stereotype,areas
address,A postal address of a customer.,7,,Sales;
category,A product category; categories form a tree.,3,,Catalog;
...

Custom templates work the same way with --template-file <path> and --var k=v for extra variables. See docs/reports.md for the full template language, functions and error messages.

Deployments

Deploy a reviewed model change with bluep deploy. The command imports the live database, creates a rollback script before changing it, reports conservative lock/rewrite risks, and records the successful deployment. Use --dry-run to inspect the exact SQL. A rollback restores schema definitions but cannot restore deleted data.

Exit codes

code meaning
0 success
1 differences found, lint errors, or model issues (check)
2 usage error
3 any other error: I/O, syntax, external command

Bulk editing

The browser grid and deterministic CSV review workflow are described in Bulk editing. Use a plan before applying a complete CSV snapshot so inserts, updates, deletions, and rejected rows can be reviewed.

Hierarchies

Use many_to_many when two entities are connected through an associative table. Generation derives the table, its combined primary key, foreign keys, supporting index, and any payload attributes. Reverse engineering remains unchanged unless --infer-relationships or --many-to-many is supplied. Review evidence before accepting inferred pull items with --accept-inferred-relationships; --yes alone never accepts them.

Use a hierarchy when one entity specialises another. Identity keeps shared-key subtype tables, rolldown writes leaf tables, and rollup writes one root table with nullable subtype fields.

Pulling changes from a database

Use bluep pull to compare a target with a database, DDL script, or another model. Review the checkbox plan before applying it. Saved plans can be reviewed as JSON. The command makes minimal text edits and can propagate physical changes to logical entities and attributes.