private ColumnWriterDetails GetDecimalColumnWriterDetails(bool isNullable, uint columnId, PropertyInfo propertyInfo, Func <object, decimal?> valueGetter) { //TODO add two options to configure Precision and Scale, via an attribute on the property, and via a fluent configuration source var precision = _defaultDecimalPrecision; var scale = _defaultDecimalScale; var state = new List <decimal?>(); var columnWriter = new DecimalWriter(isNullable, _shouldAlignNumericValues, precision, scale, _bufferFactory, columnId); return(new ColumnWriterDetails { PropertyName = propertyInfo.Name, ColumnWriter = columnWriter, AddValueToState = classInstance => { var value = valueGetter(classInstance); state.Add(value); }, WriteValuesFromState = () => { columnWriter.AddBlock(state); state.Clear(); }, ColumnType = new ColumnType { Kind = ColumnTypeKind.Decimal, Precision = (uint)precision, Scale = (uint)scale } }); }
ColumnWriterDetails GetDecimalColumnWriterDetails(bool isNullable, uint columnId, PropertyInfo propertyInfo, Func <object, decimal?> valueGetter, SerializationPropertyConfiguration propertyConfiguration) { var precision = propertyConfiguration?.DecimalPrecision ?? _defaultDecimalPrecision; var scale = propertyConfiguration?.DecimalScale ?? _defaultDecimalScale; var state = new List <decimal?>(); var columnWriter = new DecimalWriter(isNullable, _shouldAlignNumericValues, precision, scale, _bufferFactory, columnId); return(new ColumnWriterDetails { PropertyName = propertyInfo.Name, ColumnWriter = columnWriter, AddValueToState = classInstance => { var value = valueGetter(classInstance); state.Add(value); }, WriteValuesFromState = () => { columnWriter.AddBlock(state); state.Clear(); }, ColumnType = new Protocol.ColumnType { Kind = Protocol.ColumnTypeKind.Decimal, Precision = (uint)precision, Scale = (uint)scale } }); }
public void FormatValue(StringBuilder queryStringBuilder, object?value) { if (_precision == null && _scale == null) { throw new ClickHouseException(ClickHouseErrorCodes.TypeNotFullySpecified, $"Both scale and precision are required for the type \"{TypeName}\"."); } if (_scale == null) { throw new ClickHouseException(ClickHouseErrorCodes.TypeNotFullySpecified, $"Scale is required for the type \"{TypeName}\"."); } if (_precision == null) { throw new ClickHouseException(ClickHouseErrorCodes.TypeNotFullySpecified, $"Precision is required for the type \"{TypeName}\"."); } if (value == null || value is DBNull) { throw new ClickHouseException(ClickHouseErrorCodes.TypeNotSupported, $"The ClickHouse type \"{ComplexTypeName}\" does not allow null values"); } decimal outputValue = value switch { decimal theValue => theValue, long theValue => theValue, ulong theValue => theValue, int theValue => theValue, uint theValue => theValue, short theValue => theValue, ushort theValue => theValue, sbyte theValue => theValue, byte theValue => theValue, _ => throw new ClickHouseException(ClickHouseErrorCodes.TypeNotSupported, $"The type \"{value.GetType()}\" can't be converted to the ClickHouse type \"{ComplexTypeName}\"."), }; var writer = new DecimalWriter("Value", ComplexTypeName, _precision.Value, _scale.Value, new[] { outputValue }); var elementSize = GetElementSize(_precision.Value); Span <byte> buffer = stackalloc byte[elementSize]; var writeSize = writer.WriteNext(buffer); if (writeSize.Bytes != elementSize) { throw new ClickHouseException(ClickHouseErrorCodes.InternalError, "Internal error. DecimalWrite did not write expected amount of bytes."); } // Not all decimal values can be parsed: // > Real value ranges that can be stored in memory are a bit larger than specified above, which are checked only on conversion from a string. // But reinterpret_cast lets obtain any Decimal value queryStringBuilder.Append("reinterpret('"); foreach (var byteValue in buffer) { queryStringBuilder.Append("\\x"); queryStringBuilder.Append(HexDigits[byteValue >> 4]); queryStringBuilder.Append(HexDigits[byteValue & 0xF]); } queryStringBuilder.Append("', 'Decimal"); switch (elementSize) { case 4: queryStringBuilder.Append("32"); break; case 8: queryStringBuilder.Append("64"); break; case 16: queryStringBuilder.Append("128"); break; default: throw new ClickHouseException(ClickHouseErrorCodes.InternalError, "Internal error. Invalid element size."); } queryStringBuilder.Append("("); queryStringBuilder.Append(_scale.Value.ToString(CultureInfo.InvariantCulture)); queryStringBuilder.Append(")')"); }