A language server that gives dbt SQL files completion for models, sources, CTEs and typed columns. These pages explain how to use it, how the code works and what works on which platform. The Findings and Tasks tabs cover what is still broken and what to do next.
The server reads a dbt project from disk, optionally asks the warehouse what columns exist, and answers completion, hover and go-to-definition requests from the editor. It never runs dbt and never renders jinja.
with cards as (
select c.id, c.mana_cost as cost
from {{ ref('stg_cards') }} c -- ref(' completes model names
),
sets as (
select s.code, s.released_at
from {{ source('raw', 'sets') }} s -- source(' completes sources
)
select
cards.cost, -- alias. completes typed columns
sets.released_at
from cards -- from/join completes CTE names
join sets on sets.code = cards.id
ref('Lists every .sql file under the project's model paths, with its path as detail.
source('Lists source tables from the YAML files and inserts both arguments. In the second argument, lists the tables of that source. Warehouse tables with no YAML follow, marked not in sources.yml.
from / joinOffers the CTEs declared in the current file, then warehouse tables as schema.table. After from schema. it lists that schema only.
alias.Resolves the alias to a model, source, CTE or plain schema.table in the current query block and lists its columns with data types. Still fails in a few cases, listed under Findings.
Every warehouse table that matches no model and no documented source is kept and offered in completion, with its columns. Nothing has to be written in YAML first.
Names complete as soon as the files are read. The warehouse is read in one statement per database on a separate thread, and live columns follow.
Over ref('…') or source(…): the model's file or the relation it reads, then its columns with types. Over alias.column: the column's type and where it comes from, or that the relation has no such column.
Jumps from a ref('model') to the model's file. Not available for sources or columns.
dbt runWatches target/run_results.json and re-derives column lists from the compiled SQL. Can replace typed columns with untyped ones.
dbt-ls check path/to/project loads the project the way the server does and prints, per schema source, how many models got columns and types, how long it took, any error, and how many undocumented tables it found.
dbt-ls.reload rebuilds everything. dbt-ls.current_model returns the project root and the model's selector path so an editor can run it.
Neovim through vim.lsp.config. A VS Code extension that starts one server per dbt_project.yml in the workspace.
Not implemented: diagnostics, seeds, snapshots, macros, rename, formatting.
Most of the server does not depend on the warehouse. Only one thing does: reading live column names and types from it. That split is the key to making testing less tedious.
Model, source and CTE completion, go to definition, columns from YAML files, columns and types from target/catalog.json, and both commands. This includes Snowflake, BigQuery and Fabric.
No profile is needed for these.
One function per adapter in model.py. Each lists the tables of every non-system schema; a shared step then matches them to models and sources by schema, and keeps the rest as undocumented tables.
DuckDB has real load tests. Postgres, MySQL, SQL Server and Spark Connect are tested against containers. The four cloud readers are tested against stand-in clients only.
| Platform | Profile type | Parsed as | Live columns through | Sign-in it accepts | State on main |
|---|---|---|---|---|---|
| DuckDB | duckdb | duckdb | One statement, local file | None needed | Works covered by load tests |
| PostgreSQL | postgres | postgres | One statement on pg_catalog | User and password | Works container tests |
| MySQL | mysql | mysql | One statement on information_schema | User and password, key must be server | Works container tests |
| SQL Server | sqlserver | tsql | One statement over ODBC | SQL login only, key must be server | Works container tests |
| Spark | spark | spark | Spark Connect session, one request per table | None | Works container tests; needs pyspark[connect] |
| Databricks | databricks | databricks | Databricks SDK, Unity Catalog lists | Access token, or service principal OAuth | Not run stub-tested |
| Athena | athena | trino | boto3 Athena metadata | Named AWS profile or default | Not run stub-tested, paging fixed |
| Glue | glue | trino | boto3 Glue catalog | Named AWS profile or default | Not run stub-tested, paging fixed |
| Redshift | redshift | redshift | boto3 Redshift Data API, svv_columns | IAM profile only, cluster or serverless | Not run stub-tested; password profiles rejected |
| Snowflake | snowflake | snowflake | Nothing | Not applicable | No live columns |
| BigQuery | bigquery | bigquery | Nothing | Not applicable | No live columns |
| Fabric, Synapse | fabric, synapse | tsql | Nothing | Not applicable | No live columns |
| Trino | trino | trino | Nothing | Not applicable | No live columns |
| Anything else | any | postgres | Nothing | Not applicable | No live columns |
This describes main after 0.4.1. "Works" means the reader ran against a real instance in a test: DuckDB in the default run, the other four in the container tests, which CI runs on every push. The cloud platforms have not been run against a real account in any test. Not released yet: two fixes found by the container tests (the Spark extra, and SQL Server types with their precision), --profiles-dir / DBT_PROFILES_DIR support, and hover. Which cloud platforms you verified by hand over the summer is not recorded anywhere in the repo, so I could not include it.