You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ClickHouseDataReader.GetEffectiveClickHouseType(int) unwraps only NullableType. LowCardinalityType and SimpleAggregateFunctionType are pure pass-throughs on the read path (Read delegates straight to UnderlyingType.Read, FrameworkType delegates to UnderlyingType.FrameworkType), so a LowCardinality(DateTime(tz)) or SimpleAggregateFunction(any, DateTime(tz)) column decodes to a perfectly normal DateTime value — but every consumer of GetEffectiveClickHouseType sees the wrapper instead of the date/time type underneath.
Two observable consequences:
GetDateTimeOffset(ordinal) throws InvalidCastException for wrapper-wrapped date/time columns, even though GetValue(ordinal) returns a DateTime, GetFieldType(ordinal) reports System.DateTime, and the timezone is right there in the column type — so the offset is fully knowable.
SimpleAggregateFunction(...) is the case that matters most in practice: it needs no special settings and is the normal column type in AggregatingMergeTree tables. (LowCardinality(DateTime) additionally requires allow_suspicious_low_cardinality_types=1, but LowCardinality(Nullable(DateTime)) and the Decimal/DateTime64 schema cases do not.)
Note that these wrappers are already treated as transparent elsewhere in the driver — HttpParameterFormatter explicitly unwraps Nullable / LowCardinality / Variant before formatting — so the read-side accessors are the outlier.
Steps to reproduce
Run against any supported server (reproduced on 26.5.1.882).
Call reader.GetDateTimeOffset(0) on the resulting ClickHouseDataReader.
Expected behaviour
GetDateTimeOffset() should return 2024-01-15T12:30:45.0000000+01:00 — the same value it already returns for the unwrapped and Nullable-wrapped forms of the identical column — rather than throwing. Likewise GetSchemaTable() should report the underlying type's precision/scale.
The server agrees the value is a timezone-carrying DateTime:
$ curl -s --data-binary @q.sql http://clickhouse:8123/
saf v
SimpleAggregateFunction(any, DateTime('Europe/Amsterdam')) 2024-01-15 12:30:45
internalClickHouseTypeGetEffectiveClickHouseType(intordinal){vartype=RawTypes[ordinal];returntypeisNullableTypent?nt.UnderlyingType:type;// LowCardinality / SimpleAggregateFunction not unwrapped}
Consumers that then fail to match:
ClickHouseDataReader.cs:157 — GetDateTimeOffset requires is AbstractDateTimeType, otherwise throw new InvalidCastException().
Utility/SchemaDescriber.cs:59 — matches DecimalType / DateTime64Type / Time64Type for precision/scale.
Suggested fix
Unwrap the transparent wrappers in GetEffectiveClickHouseType — loop while the type is NullableType, LowCardinalityType, or SimpleAggregateFunctionType, taking UnderlyingType each time (a loop, not a single step, since these nest: LowCardinality(Nullable(DateTime)), SimpleAggregateFunction(any, Nullable(DateTime))).
Contrast cases that must keep their current behaviour:
AllowDBNull must stay keyed on the outer type being NullableType... except that it is arguably already wrong for LowCardinality(Nullable(T)); whether to change that is a separate call, and this issue does not ask for it.
GetDataTypeName must keep returning the full wrapped type name (GetClickHouseType, not the effective type).
Genuinely non-transparent wrappers (AggregateFunction, as opposed to SimpleAggregateFunction) must not be unwrapped.
A regression test belongs alongside the existing TimezoneHandlingTests / DateTimeTests coverage, asserting the wrapped forms return the same DateTimeOffset as the unwrapped control, plus a GetSchemaTable() case for SimpleAggregateFunction(sum, Decimal(18,4)).
ClickHouse Server version: 26.5.1.882 (official build, docker)
ClickHouse Server non-default settings, if any: none, except allow_suspicious_low_cardinality_types=1 on the two LowCardinality(DateTime...) probes only — the SimpleAggregateFunction cases need no settings
CREATE TABLE statements for tables involved: see the AggregatingMergeTree example above; the rest are settings-free SELECTs with no table
Sample data: inline above
Found by automated analysis of this client while working on #515, then verified in a container against a live 26.5.1 server (not by inspection alone) — reported by @polyglotAI-bot. Filed as a separate issue rather than folded into the #515 fix, since it is a distinct defect.
Describe the bug
ClickHouseDataReader.GetEffectiveClickHouseType(int)unwraps onlyNullableType.LowCardinalityTypeandSimpleAggregateFunctionTypeare pure pass-throughs on the read path (Readdelegates straight toUnderlyingType.Read,FrameworkTypedelegates toUnderlyingType.FrameworkType), so aLowCardinality(DateTime(tz))orSimpleAggregateFunction(any, DateTime(tz))column decodes to a perfectly normalDateTimevalue — but every consumer ofGetEffectiveClickHouseTypesees the wrapper instead of the date/time type underneath.Two observable consequences:
GetDateTimeOffset(ordinal)throwsInvalidCastExceptionfor wrapper-wrapped date/time columns, even thoughGetValue(ordinal)returns aDateTime,GetFieldType(ordinal)reportsSystem.DateTime, and the timezone is right there in the column type — so the offset is fully knowable.GetSchemaTable()silently dropsNumericPrecision/NumericScale/ColumnSizeforSimpleAggregateFunction-wrappedDecimalandDateTime64columns (SchemaDescriber.cs:59uses the same helper). This is the same class of gap that [backfill: ClickHouse/clickhouse-cs] GetSchemaTable() does not report DateTime64 precision (NumericScale) #438 / Report DateTime64/Time64 fractional scale in GetSchemaTable() #446 fixed for the unwrapped types; wrappers were missed.SimpleAggregateFunction(...)is the case that matters most in practice: it needs no special settings and is the normal column type inAggregatingMergeTreetables. (LowCardinality(DateTime)additionally requiresallow_suspicious_low_cardinality_types=1, butLowCardinality(Nullable(DateTime))and theDecimal/DateTime64schema cases do not.)Note that these wrappers are already treated as transparent elsewhere in the driver —
HttpParameterFormatterexplicitly unwrapsNullable/LowCardinality/Variantbefore formatting — so the read-side accessors are the outlier.Steps to reproduce
SELECT anySimpleState(toDateTime('2024-01-15 12:30:45', 'Europe/Amsterdam'))reader.GetDateTimeOffset(0)on the resultingClickHouseDataReader.Expected behaviour
GetDateTimeOffset()should return2024-01-15T12:30:45.0000000+01:00— the same value it already returns for the unwrapped andNullable-wrapped forms of the identical column — rather than throwing. LikewiseGetSchemaTable()should report the underlying type's precision/scale.The server agrees the value is a timezone-carrying
DateTime:Code example
Also reproducible on a real table column:
Error log
Probe over the whole matrix (controls first, then the wrapped forms). Each line is
GetDataTypeName/GetFieldType/GetValue/GetDateTimeOffset:GetSchemaTable()on the same connection, showing the second affected path:Root cause
ClickHouse.Driver/ADO/Readers/ClickHouseDataReader.cs:115Consumers that then fail to match:
ClickHouseDataReader.cs:157—GetDateTimeOffsetrequiresis AbstractDateTimeType, otherwisethrow new InvalidCastException().Utility/SchemaDescriber.cs:59— matchesDecimalType/DateTime64Type/Time64Typefor precision/scale.Suggested fix
Unwrap the transparent wrappers in
GetEffectiveClickHouseType— loop while the type isNullableType,LowCardinalityType, orSimpleAggregateFunctionType, takingUnderlyingTypeeach time (a loop, not a single step, since these nest:LowCardinality(Nullable(DateTime)),SimpleAggregateFunction(any, Nullable(DateTime))).Contrast cases that must keep their current behaviour:
GetFieldType/SchemaDescriber'sDataTypecolumn already resolve correctly throughFrameworkTypedelegation — don't double-unwrap there.AllowDBNullmust stay keyed on the outer type beingNullableType... except that it is arguably already wrong forLowCardinality(Nullable(T)); whether to change that is a separate call, and this issue does not ask for it.GetDataTypeNamemust keep returning the full wrapped type name (GetClickHouseType, not the effective type).AggregateFunction, as opposed toSimpleAggregateFunction) must not be unwrapped.A regression test belongs alongside the existing
TimezoneHandlingTests/DateTimeTestscoverage, asserting the wrapped forms return the sameDateTimeOffsetas the unwrapped control, plus aGetSchemaTable()case forSimpleAggregateFunction(sum, Decimal(18,4)).Configuration
Environment
main@ 7d3a2d9ClickHouse server
allow_suspicious_low_cardinality_types=1on the twoLowCardinality(DateTime...)probes only — theSimpleAggregateFunctioncases need no settingsCREATE TABLEstatements for tables involved: see theAggregatingMergeTreeexample above; the rest are settings-freeSELECTs with no tableFound by automated analysis of this client while working on #515, then verified in a container against a live 26.5.1 server (not by inspection alone) — reported by @polyglotAI-bot. Filed as a separate issue rather than folded into the #515 fix, since it is a distinct defect.