Getting Started
Install dfmt, run it in your project, and answer one screen of questions. After that every command works the same for you, your team and your CI.
1. Install
curl -fsSL https://dfmt.datoria.ai/install.sh | sh
On Windows, in PowerShell: irm https://dfmt.datoria.ai/install.ps1 | iex. The
Install page covers the supported platforms, PATH, upgrading and uninstalling.
dfmt is free and needs no account or sign-in.
2. Run it in your project
cd your-project
dfmt
The first time, there is no project setup yet, so dfmt says so and opens the setup screen. It reads the SQL under the folder, and proposes a setup:
- which dialect each folder of SQL is written in (PostgreSQL, Snowflake, T-SQL, …),
- what runs its files (psql, sqlcmd, Flyway, dbt, or nothing at all),
- the variables those files read, if any.
Check the proposal, change what is wrong, and press s to save it. The answer goes into a file
called dlab.json at the root of the project. Commit it: it is how your team and your CI get the
same result as you.
dfmt asks rather than guesses because a wrong guess does not fail. A parser that recovers from errors accepts almost any input under almost any dialect, so a guessed dialect produces a confident layout of a query you did not write.
When you leave the screen, dfmt carries on with what you asked for. Bare dfmt is a dry run: it
formats every file in memory, says what would change, and writes nothing.
3. Look, then format
dfmt diff # what would change; a browser of the changes in a terminal
dfmt write # format in place, touching only files that change
dfmt check # exit 1 if anything needs formatting (for CI)
Each takes paths too: dfmt write models/staging formats one folder, dfmt diff q.sql one file.
dfmt format q.sql prints one file formatted to stdout and writes nothing.
4. Tune it
dfmt setup
dfmt setup opens the same screen whenever you want it. Its how it is formatted side shows the
options for your project, keyword case, line width, comma style and the rest, applied live to your
own files as you change them. Save with s and every command uses them from then on.
To set one option without the screen:
dfmt options --set keywordCase=upper
dfmt options lists every option with this project's value and where it came from. The
Configuration Reference lists them all, and the Options, in order
pages show each one with examples.
What dlab.json holds
A project with one dialect needs little more than its name:
{
"dialect": "postgres",
"include": [
"**.sql",
"**.pgsql",
"**.pg"
],
"exclude": [],
"dfmt": {
"keywordCase": "upper"
}
}
The include list is the dialect's own file suffixes: PostgreSQL tooling also uses .pgsql and
.pg, while most dialects only use .sql. Formatting options go under "dfmt", and only the ones
you changed are written: the rest follow dfmt's defaults, so the project picks up better defaults
when dfmt improves. A key that is not an option is an error, reported before any file is read.
dfmt finds dlab.json by walking up from the file it is formatting, so the folder that holds it is
the project root. The file is named for the toolkit dfmt is part of rather than for dfmt: the
project setup in it is shared by every Datoria tool, so you set a project up once.
In a script or a CI job, where there is no screen to answer, write the setup in one line instead:
dfmt setup --dialect postgres
What formatting does to your SQL
dfmt parses each statement into a syntax tree and lays the tree out under your options and the line width. It works from the structure, not the text, so a one-line query and a hand-wrapped one come out the same.
Given this on one line:
with first_stage as (select id, name, created_at from raw_customers where created_at > '2024-01-01'), second_stage as (select id, name from first_stage where name is not null) select * from second_stage
dfmt produces:
with
first_stage as (
select id, name, created_at
from raw_customers
where created_at > '2024-01-01'
),
second_stage as (select id, name from first_stage where name is not null)
select *
from second_stage
The first CTE is too wide for one line, so it breaks clause by clause. The second fits, so it stays on one line. That rule applies everywhere: what fits stays on its line, what does not breaks.
dfmt also checks its own work. The output must hold the same tokens, in the same order, as the input. A run that would drop or reorder any of your SQL fails instead of writing.
A dbt project
dfmt finds a dbt project by its dbt_project.yml and reads the dialect from the adapter type in
your profiles.yml (in the project folder or in ~/.dbt/). Each model is formatted as a template:
dfmt runs the Jinja itself, lays out the SQL that comes out, and puts every {{ ref() }} and
{% if %} back where you wrote it.
You do not need dbt installed to format. One exception: if the project has a packages.yml, its
packages must be installed, because a model can call their macros. Run dbt deps once, locally and
in CI, before dfmt. dfmt stops with a message when they are missing rather than format the models
as if those macros produced nothing.
A model written like this:
with
repository_teams as (
select
{% if var('flag', true) %}
*
from {{ ref('t1') }}
{% else %}
a, b
from {{ ref('t2') }}
{% endif %}
)
select *
from repository_teams
comes back with the SQL laid out and every tag kept exactly as written:
with
repository_teams as (
select
{% if var('flag', true) %}
* from {{ ref('t1') }}
{% else %}
a, b from {{ ref('t2') }}
{% endif %}
)
select *
from repository_teams
The {% else %} branch is formatted too, even though flag is true and its SQL never ran. dfmt
gives every branch its own pass. dbt Integration covers the rest.
To print one model formatted, name it:
dfmt format --model stg_orders
Runners
dbt is one runner. dfmt also reads files the way Flyway, schemachange and Qlik send them, and
scripts written for a database's own client (psql, sqlcmd, SQL*Plus, the db2 command line and
others). It finds most runners from their project files, and you can name one per folder in
dlab.json. See Runners.
Only what changed
On a branch, format or check just the files you touched:
dfmt write --changed-since main
dfmt check --changed-since HEAD~1
The changed files are filtered through your include and exclude. If git cannot resolve the ref,
check exits 2 instead of reporting a pass for files it never looked at.
When dfmt will not format a file
dfmt leaves a file untouched, and says why, rather than guess:
- No dialect. There is no
dlab.jsonand no project above the file that names one. Rundfmtordfmt setupin a terminal to set the project up. - A Jinja file with no project to run it.
{% if x %}handed to an SQL parser does not fail; it reads as operators and names and gives a confident parse of a query nobody wrote. dfmt reads a template through the project that runs it (dbt, schemachange), looking upward from the file, and leaves the file as it is, with a warning, if there is none.
dfmt check counts both as not assessed, and fails. A file nobody could check is not a file
that passed.
Telemetry
Release builds of dfmt send one anonymous usage event per run: counts and timings, never file
names or SQL. Set DLAB_TELEMETRY=0 or DO_NOT_TRACK=1, or put "telemetry": false in
dlab.json, to turn it off. The Telemetry page lists every field.