cockroachdb / cockroachdb/cockroach
sql: sum()/avg() over INT2/INT4 returns DECIMAL instead of BIGINT, diverging from Postgres
- Dominant language
- Go
- Stars
- 32.5k
- Forks
- 4.1k
- PR merge metrics
- PR metrics pending
Description
**Describe the problem**
In PostgreSQL, the return type of `sum` depends on the input integer width:
- `sum(smallint)` / `sum(int2)` → `bigint`
- `sum(integer)` / `sum(int4)` → `bigint`
- `sum(bigint)` / `sum(int8)` → `numeric`
CockroachDB instead returns `numeric` (aka `DECIMAL`) for `sum` over *every* integer width. This is correct for `int8` inputs (Postgres also promotes those to `numeric` to avoid int64 overflow), but diverges for `int2`/`int4`, which Postgres promotes only as far as `bigint`.
The root cause is that CockroachDB defines a single `sum(int) → decimal` overload rather than Postgres's three width-specific `sum` aggregates. Narrower widths widen into the single `int` overload:
https://github.com/cockroachdb/cockroach/blob/master/pkg/sql/sem/builtins/aggregate_builtins.go#L417-L419
```go
"sum": makeBuiltin(tree.FunctionProperties{},
makeImmutableAggOverload([]*types.T{types.Int}, types.Decimal, newIntSumAggregate,
"Calculates the sum of the selected values."),
...
```
**To Reproduce**
```sql
-- Postgres returns bigint for both; CRDB returns numeric for both.
SELECT pg_typeof(sum(x::INT2)) FROM generate_series(1, 10) g(x); -- numeric
SELECT pg_typeof(sum(x::INT4)) FROM generate_series(1, 10) g(x); -- numeric
-- This case IS compatible (Postgres also returns numeric):
SELECT pg_typeof(sum(x::INT8)) FROM generate_series(1, 10) g(x); -- numeric
```
**Expected behavior**
To match Postgres:
| input | Postgres | CRDB today | expected |
|---|---|---|---|
| `sum(int2)` | `bigint` | `numeric` | `bigint` |
| `sum(int4)` | `bigint` | `numeric` | `bigint` |
| `sum(int8)` | `numeric` | `numeric` | `numeric` (unchanged) |
Summing `int2`/`int4` into `int8` cannot overflow int64 for any realistic row count, so the overflow-avoidance rationale that justifies `sum(int8) → numeric` does not apply to the narrower widths. The likely fix is width-specific `sum` overloads (`int2`/`int4 → int8`), while keeping `int8 → decimal`.
`avg` almost certainly has the analogous divergence (Postgres: `avg(int2/int4/int8) → numeric`, so `avg` may be a separate question) — worth auditing the other int-input aggregates at the same time.
**Additional context**
- Related to #26925 (general integer-width Postgres compatibility, epic CRDB-60813); this issue is the concrete aggregate-return-type instance of that broader gap.
- Prior discussion: #7414 established the "return decimal to avoid overflow" decision for `sum(int)`; #12701 (closed/stale) considered overloading `sum` to return int. #38845 ("some aggregations should promote the return type") was closed by #49900, but that only taught the *vectorized* engine to handle the existing decimal return type — it did not change the user-visible return type.
- Impact: pg-compatibility for ORMs, drivers, and applications that inspect result column types; a column typed `bigint` in Postgres arrives as `numeric` in CRDB.
Epic CRDB-60813
Jira issue: CRDB-66606
Contributor guide
Research direction
Start in pkg/sql/sem/builtins/aggregate_builtins.go at the sum overload around the linked lines, then run the SQL reproduction queries for INT2, INT4, and INT8. Done means sum over INT2 and INT4 reports bigint while INT8 remains numeric; audit the related integer-input aggregate behavior as the issue suggests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, sql
- Domain
- databases
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100