dbt-labs / dbt-labs/dbt-adapters
dbt-snowflake: catalog.json reports every numeric column as bare NUMBER, discarding precision and scale the same query already returns
- Dominant language
- Python
- Stars
- 233
- Forks
- 362
- Avg merge
- 3d 22h
- Merged PRs (30d)
- 9
Description
## Summary
`snowflake__get_catalog_columns_sql` selects `data_type` from
`INFORMATION_SCHEMA.COLUMNS` and nothing else about the type. For Snowflake, `data_type` is
`NUMBER` for **every** numeric column regardless of its declared precision and scale, so
`catalog.json` records `"NUMBER"` for a `NUMBER(12,2)` and for a `NUMBER(38,0)` alike.
`numeric_precision` and `numeric_scale` are columns of the very row being selected. They
are available, correct, and dropped.
Anything that builds on `catalog.json` to reproduce a table's shape — a local engine, a
type-diffing tool, a generated DDL — gets a column with **scale 0** and silently rounds
every value.
## The macro
`dbt-snowflake/src/dbt/include/snowflake/macros/catalog.sql`, in the
`dbt-labs/dbt-adapters` monorepo — verified against `main` on 7 August 2026:
```sql
{% macro snowflake__get_catalog_columns_sql(information_schema) -%}
select
table_catalog as "table_database",
table_schema as "table_schema",
table_name as "table_name",
column_name as "column_name",
ordinal_position as "column_index",
data_type as "column_type", -- <- NUMBER, for every numeric column
comment as "column_comment"
from {{ information_schema }}.columns
{%- endmacro %}
```
## Reproduction
Any model with a decimal column will do.
```sql
-- models/example.sql
select cast(901.75 as number(12,2)) as retail_price
```
```bash
dbt run && dbt docs generate
```
`target/catalog.json`:
```json
{ "column_name": "RETAIL_PRICE", "column_type": "NUMBER" }
```
What Snowflake already returned for that same row:
```sql
select column_name, data_type, numeric_precision, numeric_scale
from INFORMATION_SCHEMA.COLUMNS
where table_name = 'EXAMPLE';
```
```
COLUMN_NAME DATA_TYPE NUMERIC_PRECISION NUMERIC_SCALE
RETAIL_PRICE NUMBER 12 2
```
Executed on a real project, this is not an occasional case. Across 16 models with 89
numeric columns, **not one** carried precision or scale in `catalog.json`:
```
NUMBER 89 · TEXT 70 · DATE 16 · TIMESTAMP_NTZ 3 · BOOLEAN 3 · ARRAY 1 · OBJECT 1
```
It also erases scale the author wrote down explicitly: a column defined as
`cast(avg(x) as number(38,4))` is still reported as bare `NUMBER`.
## Why it matters beyond documentation
For the docs site the loss is cosmetic. For anything *consuming* the artefact it is not,
and `catalog.json` is the documented way to learn a model's column types without querying
the warehouse.
Concretely, in a tool that materialises Snowflake data locally: `NUMBER` maps to a
scale-0 decimal, and the landing table then rounds.
```
duckdb> CREATE TABLE landing ("v" DECIMAL(38,0)); -- what "NUMBER" implies
duckdb> INSERT INTO landing VALUES (901.75);
duckdb> SELECT v FROM landing; -- 902
```
It rounds. It does not raise. Every money column in the project was silently altered, and
the defect was only found by fingerprinting the same relation on both engines and noticing
that one column disagreed while the row counts matched exactly.
Anyone building on `catalog.json` has this and has no way to know.
## Suggested fix
Include the columns that are already in the result set:
```sql
data_type as "column_type",
numeric_precision as "numeric_precision",
numeric_scale as "numeric_scale",
character_maximum_length as "character_maximum_length",
```
If adding fields to the catalog schema is a larger change than it looks, an alternative
that keeps the existing shape is to compose the type string in SQL:
```sql
case
when data_type = 'NUMBER' and numeric_precision is not null
then 'NUMBER(' || numeric_precision || ',' || numeric_scale || ')'
else data_type
end as "column_type",
```
That is what a reader of `column_type` almost certainly expects it to mean, and it matches
what `describe` on a cursor already returns for the same column.
## Scope
Snowflake specifically, because `data_type` is where Snowflake puts the unparameterised
name. Other adapters may or may not have the equivalent issue depending on what their
information schema returns in that column — worth a look, but this report is about the one
that was executed.
## Workaround, for anyone who finds this before it is fixed
Do not take types from `catalog.json`. `cursor.describe()` on the Snowflake connector
returns `precision` and `scale` per column and costs no scan.
Contributor guide
Assessment
This issue has not been assessed yet.