Fix Decimal read as System.Decimal losing the column scale - #655
Merged
Merged
Conversation
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>
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The added benchmark connection owns resources but is never disposed.
Review effort: Balanced
Findings: 1
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.
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>
Contributor
There was a problem hiding this comment.
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 Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
alex-clickhouse
approved these changes
Oct 2, 2026
alex-clickhouse
left a comment
Collaborator
There was a problem hiding this comment.
LGTM, thank you for the PR!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Fixes #654.
A
Decimalcolumn read asSystem.Decimallost its scale whenever the value had trailing fractional zeros:7in aDecimal(18, 4)column came back as7minstead of7.0000m.DecimalType.ReadDecimaldivided themantissa by
10^scale, andSystem.Decimaldivision drops trailing zeros. The value is now built from themantissa and the column scale directly, the same way
(decimal)ClickHouseDecimaland the native TCP client(
DecimalColumnCodec.MakeDecimal) already do.Affected paths:
GetValue,GetDecimalandGetFieldValue<decimal>withUseCustomDecimals=false;decimalproperty read throughQueryAsync<T>, with anyUseCustomDecimalsvalue (the POCO fast path readsa
decimalproperty through the typeddecimalreader).When a value does not fit
System.Decimalat the column's scale (more than 28 fractional digits or a mantissawider than 96 bits), the existing behaviour is kept: trailing zeros are dropped only as far as needed
(
toDecimal128(7, 30)reads as7.followed by 28 zeros,toDecimal128('1000000000', 20)with scale 19), and avalue that still does not fit throws
OverflowException.Behaviour change
The numeric value does not change, but the scale does, so
ToString(),decimal.GetBitsand serialized outputdo. In particular, since #467 a scalar
decimalwritten toDynamic(or to an unhinted JSON path withJsonWriteMode.Binary) is stored with a padded scale:1.2mis stored asDecimal(9, 8). Read back asdecimal, it was1.2before this change and is1.20000000now, which matches what(decimal)clickHouseDecimalalready 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>withUseCustomDecimals=false;QueryAsync<T>into adecimalproperty 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}AsSystemDecimalbenchmarks read through aUseCustomDecimals=falseconnection; the existing
SelectDecimal*ones readClickHouseDecimaland do not reach this code. LocalClickHouse 26.9, 500k rows, median:
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