Repository navigation
Open
Labels
ADO.NETADO.NET feature parityADO.NET feature parityenhancementNew feature or requestNew feature or request
Description
A DuckDB HUGEINT is exactly an Int128, and a UHUGEINT is a UInt128. Since 1.5.6.1 the library converts through these types internally, but they aren't supported as .NET types: reading as Int128 throws, the mapped appender refuses an Int128 property, and a parameter is sent as text. This issue covers support for Int128 and UInt128 in reading, parameters and the appender.
What happens today (checked on 1.5.6.1 / develop)
| Operation | Result |
|---|---|
GetFieldValue<Int128>() on HUGEINT |
InvalidCastException: Cannot cast from BigInteger to Int128 |
GetFieldValue<UInt128>() on UHUGEINT |
InvalidCastException: Cannot cast from BigInteger to UInt128 |
GetFieldValue<Int128>() on BIGINT |
InvalidCastException: Cannot cast from Int64 to Int128 |
GetFieldValue<List<Int128>>() on HUGEINT[] |
InvalidCastException |
new DuckDBParameter(Int128.MinValue) |
DbType is Object; the value goes through the converter's ToString() fallback, as text, and DuckDB casts it. An insert into a HUGEINT column works |
row.AppendValue(Int128.MaxValue) / AppendValue(UInt128.MaxValue) |
Compiles and works: the argument converts implicitly to BigInteger?, the only overload it converts to, so the value goes through BigInteger |
DuckDBAppenderMap<T> with an Int128 property |
NotSupportedException: Type Int128 is not supported for mapping |
Reading
NumericConverter.Convert: addInt128.CreateCheckedandUInt128.CreateChecked. A HUGEINT read asInt128(and a UHUGEINT asUInt128) is then returned as it is. Every other pair is a checked conversion, so a UHUGEINT aboveInt128.MaxValue, or a negative HUGEINT read asUInt128, gives the reader's usualInvalidCastException.TypeExtensions.IsIntegralNumericType<T>: add both types, soBigIntegerVectorDataReaderandNumericVectorDataReadertake the typed path. VARINT and the narrower integer columns (TINYINT … UBIGINT) then read asInt128/UInt128too.NullableHandlerinVectorDataReaderBase: addKnown<Int128>()andKnown<UInt128>(), soInt128?has a compiled reader under Native AOT.- The non-generic
GetValue(offset, Type), which reads list items, struct fields and map values, usesConvert.ChangeType, which can't produce anInt128(it isn'tIConvertible).BigIntegerVectorDataReader.GetValueandNumericVectorDataReader.GetValueneed their own case for the two types. - Decided:
- REAL/DOUBLE as
Int128/UInt128: not supported. Refused with the usual "Cannot cast" message, as a HUGEINT read asfloat/doubleis refused today. - DECIMAL as
Int128/UInt128: supported, likeBigIntegertoday. The exact value when the fractional part is zero,InvalidCastExceptionotherwise, andInvalidCastExceptionwhen the value doesn't fit the type (a negative value asUInt128).DecimalVectorDataReaderhandlesBigIntegerin bothGetValidValueandGetValue, andInt128/UInt128need the same; every DECIMAL fits anInt128, so it can divide by the power of ten as anInt128, as the wide decimal read already does.
- REAL/DOUBLE as
- Unchanged:
GetValue()still returnsBigIntegerfor HUGEINT and UHUGEINT, andGetProviderSpecificValue()still returnsDuckDBHugeInt/DuckDBUHugeInt.
Parameters
DuckDBTypeMap: mapInt128andUInt128to aDbType(VarNumeric, as forBigInteger, or noDbTypeand a direct case in the converter).ClrToDuckDBConverter: bind withduckdb_create_hugeint/duckdb_create_uhugeintfrom the two halves, for a HUGEINT/UHUGEINT target and for a parameter with no target type. No text, and noBigIntegerin between.- Lists:
Int128[]/List<Int128>as list parameters.
Appender
AppendValue(Int128?)andAppendValue(UInt128?)onIDuckDBAppenderRowandDuckDBAppenderRow, writing the two halves directly. Adding members to a public interface breaks other implementations of it, and a new overload can make existing calls ambiguous or change which overload they bind to. That is why this goes into 2.0; the overloads still need checking for calls that become ambiguous.- The mapped appender: accept
Int128for HUGEINT, andUInt128for UHUGEINT (and possibly each for the other column, with a range check, asBigIntegeris accepted for both). - Function results: scalar and table function results of type
Int128/UInt128go through the same writers, so they're worth covering in the same change.
Tests and docs
- Cases in
NumericReaderConversionTests:- the full range of each type, both overflow directions, and widening from BIGINT/UBIGINT;
- VARINT in range and out of range;
Int128?with a NULL;List<Int128>, a struct field and a map value;- DOUBLE refused;
- DECIMAL of each storage width (16, 32, 64 and 128 bit) with and without a fraction, and negative as
UInt128.
- Parameter and appender tests, for each column type, that compare with values DuckDB produces.
docs/type-mapping.md: addInt128andUInt128to the HUGEINT and UHUGEINT rows.
Default type of HUGEINT and UHUGEINT (proposed, to decide)
Make Int128 the default .NET type of HUGEINT and UInt128 that of UHUGEINT, instead of BigInteger:
- Why:
- It's the exact type of the column;
BigIntegeris wider than the column can hold. GetValue()already goes throughInt128to build theBigInteger, and a largeBigIntegerallocates an array for its digits on top of the box.- A default type can change only in a major version.
- It matches the rest of this issue, so the type is the same for reading, parameters and the appender.
- It's the exact type of the column;
- What changes:
GetValue(),ExecuteScalar(), the indexer,GetFieldType(), the schema table andDataTablecolumns.sum()of INTEGER or BIGINT returns HUGEINT, so(BigInteger)command.ExecuteScalar()after aSUMthrows. Use(Int128)orGetFieldValue<BigInteger>()instead. This is the first item for the upgrade guide.- Neither type implements
IConvertible, soConvert.ToInt64(scalar)fails today and still fails afterwards.
- Stays the same:
- VARINT stays
BigInteger. GetFieldValue<BigInteger>()keeps working for all three.- The provider-specific types stay
DuckDBHugeInt/DuckDBUHugeInt.
- VARINT stays
- To check first: how Dapper and other mappers handle an
Int128value for alongorBigIntegerproperty.
Release
DuckDB.NET 2.0, so the AppendValue overloads can be added to IDuckDBAppenderRow, and so that parameters change from text to typed values and the default type changes in a major version.
Metadata
Metadata
Assignees
Labels
ADO.NETADO.NET feature parityADO.NET feature parityenhancementNew feature or requestNew feature or request
Activity
[-]Support Int128 and UInt128 for HUGEINT and UHUGEINT: reading, parameters and the appender[/-][+]Support Int128 and UInt128 for HUGEINT and UHUGEINT in 2.0: reading, parameters, the appender and the default type[/+]Abdulmannan111 commentedon Oct 20, 2026
Can I work on it?
Giorgi commentedon Oct 20, 2026
Yes, you can.
denis-ivanov commentedon Oct 21, 2026
denis-ivanov/DuckDB.EFCore#346
It will help to support these types in DuckDB.EFCore.