Skip to content

Fix Decimal read as System.Decimal losing the column scale - #655

Merged
alex-clickhouse merged 2 commits into
ClickHouse:mainfrom
retvain:fix/decimal-scale
Oct 2, 2026
Merged

alex-clickhouse merged 2 commits into
ClickHouse:mainfrom
retvain:fix/decimal-scale

Conversation

@retvain

@retvain retvain commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #654.

A Decimal column read as System.Decimal lost its scale whenever the value had trailing fractional zeros:
7 in a Decimal(18, 4) column came back as 7m instead of 7.0000m. DecimalType.ReadDecimal divided the
mantissa by 10^scale, and System.Decimal division drops trailing zeros. The value is now built from the
mantissa and the column scale directly, the same way (decimal)ClickHouseDecimal and the native TCP client
(DecimalColumnCodec.MakeDecimal) already do.

Affected paths:

  • GetValue, GetDecimal and GetFieldValue<decimal> with UseCustomDecimals=false;
  • a decimal property read through QueryAsync<T>, with any UseCustomDecimals value (the POCO fast path reads
    a decimal property through the typed decimal reader).

When a value does not fit System.Decimal at the column's scale (more than 28 fractional digits or a mantissa
wider than 96 bits), the existing behaviour is kept: trailing zeros are dropped only as far as needed
(toDecimal128(7, 30) reads as 7. followed by 28 zeros, toDecimal128('1000000000', 20) with scale 19), and a
value that still does not fit throws OverflowException.

Behaviour change

The numeric value does not change, but the scale does, so ToString(), decimal.GetBits and serialized output
do. In particular, since #467 a scalar decimal written to Dynamic (or to an unhinted JSON path with
JsonWriteMode.Binary) is stored with a padded scale: 1.2m is stored as Decimal(9, 8). Read back as
decimal, it was 1.2 before this change and is 1.20000000 now, which matches what (decimal)clickHouseDecimal
already returned.

Tests

ClickHouse.Driver.Tests/Types/DecimalReadScaleTests.cs: 18 cases against a real server (all four widths, zero,
negative values including the Decimal64 minimum, scale 0, scale 28 and above, a mantissa wider than 96 bits), each run through

  • GetValue / GetDecimal / GetFieldValue<decimal> with UseCustomDecimals=false;
  • QueryAsync<T> into a decimal property with the default settings.

The existing type matrix compares decimals by value only, so it does not catch a lost scale. Without the fix,
30 of the 36 tests fail. Passing on net9.0 and net6.0.

Benchmark

New SelectColumn.SelectDecimal{64,128,256}AsSystemDecimal benchmarks read through a UseCustomDecimals=false
connection; the existing SelectDecimal* ones read ClickHouseDecimal and do not reach this code. Local
ClickHouse 26.9, 500k rows, median:

Method Before After Allocated
SelectDecimal64AsSystemDecimal 33.00 ms 27.87 ms 18 KB → 18 KB
SelectDecimal128AsSystemDecimal 42.48 ms 40.48 ms 14972 KB → 14972 KB
SelectDecimal256AsSystemDecimal 47.04 ms 45.80 ms 14972 KB → 14972 KB
SelectDecimal64 (unchanged code, for scale) 32.44 ms 33.06 ms —

No regression and no new allocations. The differences are within run-to-run noise (about ±5% on the unchanged
rows), so I am not claiming a speed-up.

Checklist

  • Unit and integration tests covering the common scenarios were added
  • A human-readable description of the changes was provided to include in CHANGELOG

Reading a Decimal column as System.Decimal divided the mantissa by
10^scale, and decimal division drops trailing zeros, so 7 in a
Decimal(18, 4) column came back as 7 instead of 7.0000. The value is now
built from the mantissa and the column scale, as the ClickHouseDecimal
conversion and the TCP client already do. This affected reads with
UseCustomDecimals=false and decimal properties read through
QueryAsync<T> with any setting.

Adds benchmarks that read decimals through a UseCustomDecimals=false
connection, which the existing SelectDecimal* benchmarks do not reach.

Fixes: ClickHouse#654

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 2, 2026 06:50
@CLAassistant

CLAassistant commented Oct 2, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The added benchmark connection owns resources but is never disposed.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Fixes HTTP client decimal deserialization to preserve the ClickHouse column scale when representable by System.Decimal.

Changes:

  • Constructs decimals directly from mantissa and scale.
  • Adds integration coverage and performance benchmarks.
  • Updates documentation and changelog.
File Description
ClickHouse.Driver/​Types/​DecimalType.cs Preserves decimal scale during reads.
ClickHouse.Driver.Tests/​Types/​DecimalReadScaleTests.cs Tests scale retention and range trimming.
ClickHouse.Driver.Benchmark/​SelectColumn.cs Benchmarks System.Decimal decoding.
docs/​http.mdx Documents scale-preserving behavior.
changelog.d/​654-decimal-scale.fixes.md Records the bug fix.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread ClickHouse.Driver.Benchmark/SelectColumn.cs
Address review on ClickHouse#655: both connections own an HTTP client, so release
them when the benchmark finishes, as ReadValueBenchmark does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The implementation is consistent with existing decimal conversion logic and includes thorough tests, documentation, benchmarks, and resource cleanup.

Review effort: Balanced
Findings: None

Resolved since last review (1)

@codecov

codecov Bot commented Oct 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@alex-clickhouse alex-clickhouse left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you for the PR!

@alex-clickhouse
alex-clickhouse merged commit c416dc2 into ClickHouse:main Oct 2, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Decimal read as System.Decimal loses the column scale (7 instead of 7.0000)

4 participants