Skip to content

Fix InvalidCastException when summing double/float columns (#46) - #48

Merged
alex-clickhouse merged 2 commits into
mainfrom
worktree-fix-sum-double-invalidcast
Jul 7, 2026
Merged

Fix InvalidCastException when summing double/float columns (#46)#48
alex-clickhouse merged 2 commits into
mainfrom
worktree-fix-sum-double-invalidcast

Conversation

@alex-clickhouse

Copy link
Copy Markdown
Collaborator

Summary

Fixes #46. Sum/SumAsync over a double or float column threw System.InvalidCastException before ever reaching ClickHouse:

System.InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.Double'.
   at ClickHouse.EntityFrameworkCore.Storage.Internal.Mapping.ClickHouseDoubleTypeMapping.GenerateNonNullSqlLiteral(Object value)

Root cause. EF Core wraps a top-level aggregate so the empty-result case returns 0, and supplies that fallback as a boxed Int32 carrying the column's Float64/Float32 type mapping. GenerateNonNullSqlLiteral unboxed it with a hard (double)/(float) cast, which throws for any runtime type other than the exact target.

While adding an end-to-end test I hit a second, related ClickHouse-specific issue: sum(Float32) widens to Float64, and the driver's GetFloat() refuses to downcast the returned Double — so summing a float column failed on the read path too.

Changes

  • ClickHouseDoubleTypeMappingConvert.ToDouble(...) instead of an unbox cast (NaN/±Infinity handling preserved).
  • ClickHouseFloatTypeMappingConvert.ToSingle(...) in the literal generator, plus a GetValue() + Convert.ToSingle() read path, mirroring ClickHouseIntegerTypeMapping (which already solves the same widening problem for COUNTUInt64).
  • The Double read path is intentionally left alone — Float64 is the widest float type ClickHouse aggregates return, so GetDouble() always works.

Type-system audit

I checked the whole type system for both bug classes:

  • Literal hard-cast: only Double and Float were affected. Decimal/BigDecimal/BigInteger already use Convert.To*; integer mappings have no literal override; non-numeric mappings aren't Sum/Average targets.
  • Aggregate read-path widening: a throwaway probe ran Sum/Average/Min/Max over every numeric type (small, boundary, and empty-result cases). With the fix in place all variants pass — integer mappings already handle their widening, and Decimal128 reads back fine within .NET decimal range.

Tests

  • TypeMappingLiteralTests — boxed-Int32 literal for Double & Float (fail on the old cast).
  • FloatSpecialValueTestsSumAsync over Float64/Float32 with matches and with a no-match predicate.
  • AllTypesQueryTests — extended the Sum over Double gives InvalidCastException #46 guard across the adjacent types: Sum over Decimal (matches + empty), Sum over Int32 (widen-to-Int64 read) and Int64 (empty), and Average over Int32 (→ double).

All affected suites pass (85 tests); full solution builds with 0 errors. CHANGELOG.md and RELEASENOTES.md updated.

🤖 Generated with Claude Code

`Sum`/`SumAsync` over a `double` or `float` column threw
`InvalidCastException`. EF Core wraps a top-level aggregate so the empty
case returns 0, supplying that fallback as a boxed `Int32` carrying the
`Float64`/`Float32` mapping; `GenerateNonNullSqlLiteral` unboxed it with a
hard `(double)`/`(float)` cast and threw.

Both float literal generators now convert instead of unboxing. The
`Float32` read path also converts (GetValue + Convert.ToSingle), since
ClickHouse widens `sum(Float32)` to `Float64` and the driver's
`GetFloat()` refuses to downcast the returned Double.

Audited the rest of the type system: the Double read path is fine
(Float64 is the widest float type) and the integer/decimal mappings
already handle boxed fallbacks and aggregate widening. Added regression
tests for the fixed mappings plus the adjacent decimal/integer aggregate
paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Fixes ClickHouse EF Core type-mapping edge cases that caused Sum/SumAsync over double/float columns to throw InvalidCastException before or during result materialization, aligning literal generation and Float32 reading with EF Core’s boxed fallback behavior and ClickHouse aggregate widening.

Changes:

  • Updated Float64/Float32 SQL literal generation to use Convert.ToDouble/ToSingle (avoids unboxing casts when EF supplies a boxed Int32 fallback 0).
  • Updated Float32 materialization to read via DbDataReader.GetValue() and Convert.ToSingle() (handles sum(Float32) returning Float64).
  • Added regression coverage across literal generation and end-to-end aggregate queries; documented fix in changelog/release notes.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
test/EFCore.ClickHouse.Tests/TypeMappingLiteralTests.cs Adds regression tests ensuring boxed-Int32 fallback literals for Float32/Float64 don’t throw and don’t emit unnecessary casts.
test/EFCore.ClickHouse.Tests/FloatSpecialValueTests.cs Adds end-to-end SumAsync coverage for Float64/Float32, including empty-result (0) cases.
test/EFCore.ClickHouse.Tests/AllTypesQueryTests.cs Extends aggregate regression coverage to adjacent numeric types and widening behaviors (decimal, int32/int64, avg->double).
src/EFCore.ClickHouse/Storage/Internal/Mapping/ClickHouseFloatTypeMapping.cs Fixes Float32 literal generation and materialization by converting instead of unboxing / typed reads.
src/EFCore.ClickHouse/Storage/Internal/Mapping/ClickHouseDoubleTypeMapping.cs Fixes Float64 literal generation by converting instead of unboxing.
RELEASENOTES.md Documents the user-visible bug fix for Sum/SumAsync over float/double.
CHANGELOG.md Records the same fix details for v0.3.0 bug fixes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

v0.3.0 is already released, so the bug-fix entry belongs under the next
version rather than the shipped one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@alex-clickhouse
alex-clickhouse merged commit 321ec4b into main Jul 7, 2026
3 checks passed
@alex-clickhouse
alex-clickhouse deleted the worktree-fix-sum-double-invalidcast branch July 7, 2026 14:55
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.

Sum over Double gives InvalidCastException

2 participants