preen — DuckDB SQL for VS Code

Write, check and run DuckDB SQL without leaving the editor. preen lints
and formats DuckDB SQL with a parser built from DuckDB's own grammar, and
runs your scripts through your own duckdb CLI — the exact version you
installed, not a copy bundled inside the editor.
Built for people who move and shape data in SQL: ETL scripts, migrations,
analytics notebooks-as-files. Works in VS Code, Cursor, VSCodium and other
forks.
What you get
- Run SQL —
Cmd/Ctrl+Enter runs the statement under the cursor (or
everything you selected); Cmd/Ctrl+Shift+Enter runs the file. Results
open beside the editor, one block per statement.
- Lint as you type — 27 DuckDB-aware rules, from real bugs
(
x = NULL, a UNION that silently mispairs columns, an INSERT that
pairs columns by position) to DuckDB idioms (GROUP BY ALL,
QUALIFY, FILTER, count()).
- Quick fixes —
Cmd/Ctrl+. on a squiggle. Fixes that could change a
query's results are labelled unsafe so you decide.
- Format — Format Document gives one consistent DuckDB style,
keywords uppercase, lists broken one item per line when they don't fit.
- Highlighting that knows DuckDB —
PIVOT, QUALIFY, ASOF,
SUMMARIZE and friends, and a table called data coloured as a table,
not a keyword. Nothing inside a comment is ever highlighted as code.
Quick start
Install this extension. Linting, formatting and highlighting work
immediately — nothing else to install.
To run SQL, install the DuckDB CLI
(macOS: brew install duckdb; others: see
duckdb.org) and, if the editor
doesn't find it on its own, set its path:
"preen.duckdb.path": "/opt/homebrew/bin/duckdb"
Open a .sql file, put the cursor on a statement, press
Cmd/Ctrl+Enter.
The status bar shows the DuckDB version in use and the database; click it
to switch between :memory: and a database file.
Running SQL
| key or command |
what it does |
Cmd/Ctrl+Enter |
run the statement at the cursor, or every statement the selection touches |
Cmd/Ctrl+Shift+Enter |
run the whole file |
| ▶ in the editor title bar |
the same, with the mouse |
| preen: Choose DuckDB Database |
:memory: or a .duckdb file |
| preen: Restart DuckDB Session |
start fresh (also stops a long query) |
- One session stays open, like a terminal:
TEMP tables, SET options
and in-memory data survive from one run to the next.
- Statements run one at a time and each result shows under its own
statement. A
; inside a string or comment never splits a statement.
- A run stops at the first error and marks it in the editor where
DuckDB reported it.
- Relative paths in SQL (
read_csv('data/orders.csv')) resolve from
the workspace folder, as if you ran duckdb from the project root.
- DuckDB 1.5 and 2.0 both work — point
preen.duckdb.path at the one
you want. A database file written by 2.0 cannot be opened by 1.5; if
that's why a run fails, the error says so.
Rules
Correctness and DuckDB idioms:
| code |
what it catches |
fix |
| MOD002 |
x = NULL is never true; use IS NULL |
unsafe |
| MOD005 |
DISTINCT together with GROUP BY |
— |
| MOD007 |
NATURAL JOIN re-pairs silently when columns change |
— |
| MOD016 |
a CTE that is never used |
safe |
| MOD026 |
INSERT INTO t SELECT … pairs columns by position; use BY NAME |
unsafe |
| MOD027 |
UNION arms with the same columns in a different order |
unsafe |
| MOD028 |
DROP … IF EXISTS then CREATE → CREATE OR REPLACE |
safe |
| MOD001, 003, 006, 013–015, 021 |
DuckDB idioms: count(), GROUP BY ALL, COALESCE, ORDER BY ALL, FILTER, CASE x WHEN, QUALIFY |
mostly safe |
| MOD004, 008, 009, 011, 012 |
order and naming hazards: ORDER BY in subqueries and views, bare UNION, GROUP BY 1, redundant aliases |
some |
House style: MOD010 comma joins, MOD017 SELECT * into tables, MOD018
constraints on bulk loads, MOD019 huge IN lists, MOD020 1_000_000,
MOD022 FROM-first, MOD023 x: expr aliases, MOD025 SELECT * in final
queries.
The editor shows every rule out of the box; remove codes from
preen.select to hide them. (On the preen command line the house-style
rules are off unless you select them.)
Silence one finding with a comment on or above the statement:
-- preen: ignore[MOD026].
Settings
| setting |
default |
|
preen.duckdb.path |
duckdb on PATH |
the CLI that runs your SQL |
preen.duckdb.database |
:memory: |
or a database file (relative to the workspace) |
preen.duckdb.maxRows |
1000 |
rows shown per result; the count is always exact |
preen.select |
every rule |
the rule codes to show |
preen.path |
the bundled binary |
use a different preen build |
A preen.toml in your project (the same file the preen command line
reads) sets formatting options and rule severities, so the editor and CI
never disagree.
Privacy
preen makes no network calls and collects no telemetry. The SQL you run
goes only to the duckdb CLI you chose; what that SQL does (for example,
reading from S3) is up to you.
Not yet
No autocomplete or hover yet, no schema browser, and no cancel button
(Restart DuckDB Session stops a running query).
preen is made by Meridian Tech Labs. It is
an independent project, not affiliated with or endorsed by DuckDB Labs or
the DuckDB Foundation. DuckDB is a trademark of the DuckDB Foundation.
MIT licensed; see ThirdPartyNotices.txt for DuckDB's license, whose
grammar preen's parser is built from.
| |