private object TryConvertTo(object data, ClickHouseType type)
        {
            switch (type.TypeCode)
            {
            case ClickHouseTypeCode.Nullable:
                return(data is null || data is DBNull ? data : TryConvertTo(data, ((NullableType)type).UnderlyingType));

            case ClickHouseTypeCode.UUID:
                return(Guid.TryParse((string)data, out var guid) ? guid : data);

            case ClickHouseTypeCode.IPv4:
            case ClickHouseTypeCode.IPv6:
                return(IPAddress.TryParse((string)data, out var address) ? address : data);

            case ClickHouseTypeCode.Date:
                return(DateTime.TryParseExact((string)data, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var date)
                        ? date
                        : data);

            case ClickHouseTypeCode.DateTime:
            case ClickHouseTypeCode.DateTime64:
                return(DateTime.Parse((string)data, CultureInfo.InvariantCulture, DateTimeStyles.None));

            case ClickHouseTypeCode.Tuple:
                var tt = (TupleType)type;
                return(tt.MakeTuple((object[])data));

            default:
                break;
            }
            return(data);
        }
示例#2
0
        private object ConvertString(string item, ClickHouseType typeInfo)
        {
            switch (typeInfo)
            {
            case ArrayType ati:
                return(item
                       .Trim('[', ']')
                       .Split(',')
                       .Select(v => ConvertString(v, ati.UnderlyingType))
                       .ToArray());

            case TupleType tti:
                return(ParseTuple(item, tti));

            case NothingType ti:
                return(item == "\\N" ? DBNull.Value : throw new InvalidOperationException());

            case NullableType nti:
                return(item == "NULL" ? DBNull.Value : ConvertString(item, nti.UnderlyingType));

            case PlainDataType <Guid> _:
                return(new Guid(item));

            case PlainDataType <IPAddress> _:
                return(IPAddress.Parse(item));

            default:
                return(Convert.ChangeType(Regex.Unescape(item), typeInfo.FrameworkType, CultureInfo.InvariantCulture));
            }
        }
        private void ReadHeaders()
        {
            // Read starting tag
            AssertEquals(true, jsonReader.Read());
            AssertEquals(JsonToken.StartObject, jsonReader.TokenType);

            // Read 'meta' property
            AssertEquals(true, jsonReader.Read());
            AssertEquals(JsonToken.PropertyName, jsonReader.TokenType);
            AssertEquals("meta", jsonReader.Value);
            AssertEquals(true, jsonReader.Read());
            var columns = serializer.Deserialize <JsonColumnRecord[]>(jsonReader);

            FieldNames = new string[columns.Length];
            RawTypes   = new ClickHouseType[columns.Length];

            for (var i = 0; i < columns.Length; i++)
            {
                FieldNames[i] = columns[i].Name;
                RawTypes[i]   = TypeConverter.ParseClickHouseType(columns[i].Type);
            }

            // Read start of 'data' property
            AssertEquals(true, jsonReader.Read());
            AssertEquals(JsonToken.PropertyName, jsonReader.TokenType);
            AssertEquals("data", jsonReader.Value);

            // Read start of data array tag
            AssertEquals(true, jsonReader.Read());
            AssertEquals(JsonToken.StartArray, jsonReader.TokenType);
            AssertEquals(true, jsonReader.Read());
            hasMore = jsonReader.TokenType == JsonToken.StartArray;
        }
        internal static string Format(ClickHouseType type, object value)
        {
            return(type.TypeCode switch
            {
                var simpleType when
                simpleType == ClickHouseTypeCode.UInt8 ||
                simpleType == ClickHouseTypeCode.UInt16 ||
                simpleType == ClickHouseTypeCode.UInt32 ||
                simpleType == ClickHouseTypeCode.UInt64 ||
                simpleType == ClickHouseTypeCode.Int8 ||
                simpleType == ClickHouseTypeCode.Int16 ||
                simpleType == ClickHouseTypeCode.Int32 ||
                simpleType == ClickHouseTypeCode.Int64 => value.ToString(),

                var floatType when
                floatType == ClickHouseTypeCode.Float32 ||
                floatType == ClickHouseTypeCode.Float64 => FormatFloat(value),

                ClickHouseTypeCode.Decimal => FormatDecimal(type, value),

                var stringType when
                stringType == ClickHouseTypeCode.String ||
                stringType == ClickHouseTypeCode.FixedString ||
                stringType == ClickHouseTypeCode.LowCardinality ||
                stringType == ClickHouseTypeCode.Enum8 ||
                stringType == ClickHouseTypeCode.Enum16 ||
                stringType == ClickHouseTypeCode.UUID => value.ToString().Escape(),

                ClickHouseTypeCode.Nothing => $"null",

                ClickHouseTypeCode.Date when value is DateTime date => $"'{date:yyyy-MM-dd}'",
                ClickHouseTypeCode.DateTime when type is AbstractDateTimeType dateTimeType && value is DateTime dateTime =>
                dateTimeType.TimeZone == null
                        ? $"'{dateTime:yyyy-MM-dd HH:mm:ss}'"
                        : $"'{dateTime.ToUniversalTime():yyyy-MM-dd HH:mm:ss}'",
                ClickHouseTypeCode.DateTime64 when type is DateTime64Type dateTimeType && value is DateTime dateTime =>
                dateTimeType.TimeZone == null
                        ? $"'{dateTime:yyyy-MM-dd HH:mm:ss.fff}'"
                        : $"'{dateTime.ToUniversalTime():yyyy-MM-dd HH:mm:ss.fff}'",

                ClickHouseTypeCode.IPv4 when value is IPAddress iPAddressValue => $"toIPv4({iPAddressValue.ToString().Escape()})",
                ClickHouseTypeCode.IPv6 when value is IPAddress iPAddressValue => $"toIPv6({iPAddressValue.ToString().Escape()})",

                ClickHouseTypeCode.Nullable when type is NullableType nullableType =>
                value is null || value == DBNull.Value ?
                "null" :
                $"{Format(nullableType.UnderlyingType, value)}",

                ClickHouseTypeCode.Array when type is ArrayType arrayType && value is IEnumerable enumerable =>
                $"[{string.Join(",", enumerable.Cast<object>().Select(obj => Format(arrayType.UnderlyingType, obj)))}]",

                ClickHouseTypeCode.Tuple when type is TupleType tupleType && value is ITuple tuple =>
                $"({string.Join(",", tupleType.UnderlyingTypes.Select((x, i) => Format(x, tuple[i])))})",

                _ => throw new NotSupportedException($"Cannot convert value {value} to type {type.TypeCode}")
            });
        internal static ClickHouseType[] GetClickHouseColumnTypes(this ClickHouseDataReader reader)
        {
            var count = reader.FieldCount;
            var names = new ClickHouseType[count];

            for (int i = 0; i < count; i++)
            {
                names[i] = reader.GetClickHouseType(i);
            }

            return(names);
        }
示例#6
0
        internal static string Format(ClickHouseType type, object value)
        {
            return(type.TypeCode switch
            {
                var simpleType when
                simpleType == ClickHouseTypeCode.UInt8 ||
                simpleType == ClickHouseTypeCode.UInt16 ||
                simpleType == ClickHouseTypeCode.UInt32 ||
                simpleType == ClickHouseTypeCode.UInt64 ||
                simpleType == ClickHouseTypeCode.Int8 ||
                simpleType == ClickHouseTypeCode.Int16 ||
                simpleType == ClickHouseTypeCode.Int32 ||
                simpleType == ClickHouseTypeCode.Int64 => Convert.ToString(value, CultureInfo.InvariantCulture),

                var floatType when
                floatType == ClickHouseTypeCode.Float32 ||
                floatType == ClickHouseTypeCode.Float64 => FormatFloat(value),

                ClickHouseTypeCode.Decimal when value is decimal decimalValue => decimalValue.ToString(CultureInfo.InvariantCulture),

                var stringType when
                stringType == ClickHouseTypeCode.String ||
                stringType == ClickHouseTypeCode.FixedString ||
                stringType == ClickHouseTypeCode.LowCardinality ||
                stringType == ClickHouseTypeCode.Enum8 ||
                stringType == ClickHouseTypeCode.Enum16 ||
                stringType == ClickHouseTypeCode.UUID ||
                stringType == ClickHouseTypeCode.IPv4 ||
                stringType == ClickHouseTypeCode.IPv6 => value.ToString(),

                ClickHouseTypeCode.Nothing => $"null",

                ClickHouseTypeCode.Date when value is DateTime date => $"{date:yyyy-MM-dd}",
                ClickHouseTypeCode.DateTime when type is DateTimeType dateTimeType && value is DateTime dateTime =>
                dateTimeType.TimeZone == null
                        ? $"{dateTime:yyyy-MM-dd HH:mm:ss}"
                        : $"{dateTime.ToUniversalTime():yyyy-MM-dd HH:mm:ss}",
                ClickHouseTypeCode.DateTime64 when type is DateTime64Type dateTimeType && value is DateTime dateTime =>
                dateTimeType.TimeZone == null
                        ? $"{dateTime:yyyy-MM-dd HH:mm:ss.fffffff}"
                        : $"{dateTime.ToUniversalTime():yyyy-MM-dd HH:mm:ss.fffffff}",

                ClickHouseTypeCode.Nullable when type is NullableType nullableType => value is null || value is DBNull ? "null" : $"{Format(nullableType.UnderlyingType, value)}",

                ClickHouseTypeCode.Array when type is ArrayType arrayType && value is IEnumerable enumerable =>
                $"[{string.Join(",", enumerable.Cast<object>().Select(obj => InlineParameterFormatter.Format(arrayType.UnderlyingType, obj)))}]",

                ClickHouseTypeCode.Tuple when type is TupleType tupleType && value is ITuple tuple =>
                $"({string.Join(",", tupleType.UnderlyingTypes.Select((x, i) => InlineParameterFormatter.Format(x, tuple[i])))})",

                _ => throw new NotSupportedException($"Cannot convert value {value} to type {type.TypeCode}")
            });
        private void ReadHeaders()
        {
            var count = reader.Read7BitEncodedInt();

            FieldNames = new string[count];
            RawTypes   = new ClickHouseType[count];
            CurrentRow = new object[count];

            for (var i = 0; i < count; i++)
            {
                FieldNames[i] = reader.ReadString();
            }

            for (var i = 0; i < count; i++)
            {
                var chType = reader.ReadString();
                RawTypes[i] = TypeConverter.ParseClickHouseType(chType);
            }
        }
示例#8
0
        private void ReadHeaders()
        {
            var names = inputReader.ReadLine().Split('\t');
            var types = inputReader.ReadLine().Split('\t');

            if (names.Length != types.Length)
            {
                throw new InvalidOperationException($"Count mismatch between names ({names.Length}) and types ({types.Length})");
            }

            var fieldCount = names.Length;

            RawTypes   = new ClickHouseType[fieldCount];
            FieldNames = new string[fieldCount];

            names.CopyTo(FieldNames, 0);
            for (var i = 0; i < fieldCount; i++)
            {
                RawTypes[i] = TypeConverter.ParseClickHouseType(Regex.Unescape(types[i]));
            }
        }
        internal static string Format(ClickHouseType type, object value)
        {
            switch (type.TypeCode)
            {
            case ClickHouseTypeCode.UInt8:
            case ClickHouseTypeCode.UInt16:
            case ClickHouseTypeCode.UInt32:
            case ClickHouseTypeCode.UInt64:
            case ClickHouseTypeCode.Int8:
            case ClickHouseTypeCode.Int16:
            case ClickHouseTypeCode.Int32:
            case ClickHouseTypeCode.Int64:
                return(Convert.ToString(value, CultureInfo.InvariantCulture));

            case ClickHouseTypeCode.Float32:
            case ClickHouseTypeCode.Float64:
                return(FormatFloat(value));

            case ClickHouseTypeCode.Decimal:
                return(FormatDecimal(type, value));

            case ClickHouseTypeCode.String:
            case ClickHouseTypeCode.FixedString:
            case ClickHouseTypeCode.LowCardinality:
            case ClickHouseTypeCode.Enum8:
            case ClickHouseTypeCode.Enum16:
                return(value.ToString().Escape());

            case ClickHouseTypeCode.UUID:
                return($"toUUID({value.ToString().Escape()})");

            case ClickHouseTypeCode.Nothing:
                return("null");

            case ClickHouseTypeCode.Date when value is DateTime:
                return($"toDate('{value:yyyy-MM-dd}')");

            case ClickHouseTypeCode.DateTime when type is AbstractDateTimeType dateTimeType && value is DateTime dateTime:
                if (dateTimeType.TimeZone != null)
                {
                    dateTime = dateTime.ToUniversalTime();
                }
                return($"toDateTime('{dateTime:yyyy-MM-dd HH:mm:ss}')");

            case ClickHouseTypeCode.DateTime64 when type is DateTime64Type dateTimeType && value is DateTime dateTime:
                if (dateTimeType.TimeZone != null)
                {
                    dateTime = dateTime.ToUniversalTime();
                }
                return($"toDateTime64('{dateTime:yyyy-MM-dd HH:mm:ss.fffffff}', 7)");

            case ClickHouseTypeCode.IPv4: return($"toIPv4({FormatIPAddress(value)})");

            case ClickHouseTypeCode.IPv6: return($"toIPv6({FormatIPAddress(value)})");

            case ClickHouseTypeCode.Nullable:
                var nullableType = (NullableType)type;
                return(value is null || value == DBNull.Value ? "null" : $"{Format(nullableType.UnderlyingType, value)}");

            case ClickHouseTypeCode.Array:
                var arrayType = (ArrayType)type;
                var array     = ((IEnumerable)value).Cast <object>().Select(obj => Format(arrayType.UnderlyingType, obj));
                return($"[{string.Join(",", array)}]");

            case ClickHouseTypeCode.Tuple:
                var tupleType = (TupleType)type;
                var tuple     = (ITuple)value;
                return($"({string.Join(",", tupleType.UnderlyingTypes.Select((x, i) => Format(x, tuple[i])))})");

            default:
                throw new NotSupportedException($"Cannot convert value {value} to type {type.TypeCode}");
            }
        }
        public object ReadValue(ClickHouseType databaseType, bool nullAsDbNull)
        {
            switch (databaseType.TypeCode)
            {
            case ClickHouseTypeCode.UInt8:
                return(reader.ReadByte());

            case ClickHouseTypeCode.UInt16:
                return(reader.ReadUInt16());

            case ClickHouseTypeCode.UInt32:
                return(reader.ReadUInt32());

            case ClickHouseTypeCode.UInt64:
                return(reader.ReadUInt64());

            case ClickHouseTypeCode.Int8:
                return(reader.ReadSByte());

            case ClickHouseTypeCode.Int16:
                return(reader.ReadInt16());

            case ClickHouseTypeCode.Int32:
                return(reader.ReadInt32());

            case ClickHouseTypeCode.Int64:
                return(reader.ReadInt64());

            case ClickHouseTypeCode.Float32:
                return(reader.ReadSingle());

            case ClickHouseTypeCode.Float64:
                return(reader.ReadDouble());

            case ClickHouseTypeCode.String:
                return(reader.ReadString());

            case ClickHouseTypeCode.FixedString:
                var stringInfo = (FixedStringType)databaseType;
                return(Encoding.UTF8.GetString(reader.ReadBytes(stringInfo.Length)));

            case ClickHouseTypeCode.Array:
                var arrayTypeInfo = (ArrayType)databaseType;
                var length        = reader.Read7BitEncodedInt();
                var data          = new object[length];
                for (var i = 0; i < length; i++)
                {
                    data[i] = ReadValue(arrayTypeInfo.UnderlyingType, nullAsDbNull);
                }

                return(data);

            case ClickHouseTypeCode.Nullable:
                var nullableTypeInfo = (NullableType)databaseType;
                if (reader.ReadByte() > 0)
                {
                    return(nullAsDbNull ? DBNull.Value : null);
                }
                else
                {
                    return(ReadValue(nullableTypeInfo.UnderlyingType, nullAsDbNull));
                }

            case ClickHouseTypeCode.Date:
                var days = reader.ReadUInt16();
                return(TypeConverter.DateTimeEpochStart.AddDays(days));

            case ClickHouseTypeCode.DateTime:
                var seconds = reader.ReadUInt32();
                return(TypeConverter.DateTimeEpochStart.AddSeconds(seconds));

            case ClickHouseTypeCode.DateTime64:
                var dt64t   = (DateTime64Type)databaseType;
                var chTicks = reader.ReadInt64();
                // 7 is a 'magic constant' - Log10 of TimeSpan.TicksInSecond
                return(TypeConverter.DateTimeEpochStart.AddTicks((long)MathUtils.ShiftDecimalPlaces(chTicks, 7 - dt64t.Scale)));

            case ClickHouseTypeCode.UUID:
                // Byte manipulation because of ClickHouse's weird GUID implementation
                var bytes = new byte[16];
                reader.Read(bytes, 6, 2);
                reader.Read(bytes, 4, 2);
                reader.Read(bytes, 0, 4);
                reader.Read(bytes, 8, 8);
                Array.Reverse(bytes, 8, 8);
                return(new Guid(bytes));

            case ClickHouseTypeCode.IPv4:
                var ipv4bytes = reader.ReadBytes(4);
                Array.Reverse(ipv4bytes);
                return(new IPAddress(ipv4bytes));

            case ClickHouseTypeCode.IPv6:
                var ipv6bytes = reader.ReadBytes(16);
                return(new IPAddress(ipv6bytes));

            case ClickHouseTypeCode.Tuple:
                var tupleTypeInfo = (TupleType)databaseType;
                var count         = tupleTypeInfo.UnderlyingTypes.Length;
                var contents      = new object[count];
                for (var i = 0; i < count; i++)
                {
                    // Underlying data in Tuple should always be null, not DBNull
                    contents[i] = ReadValue(tupleTypeInfo.UnderlyingTypes[i], false);
                }
                return(tupleTypeInfo.MakeTuple(contents));

            case ClickHouseTypeCode.Decimal:
                var decimalTypeInfo = (DecimalType)databaseType;
                var value           = new BigInteger(reader.ReadBytes(decimalTypeInfo.Size));
                return(MathUtils.ShiftDecimalPlaces((decimal)value, -decimalTypeInfo.Scale));

            case ClickHouseTypeCode.Nothing:
                break;

            case ClickHouseTypeCode.Nested:
                throw new NotSupportedException("Nested types cannot be read directly");

            case ClickHouseTypeCode.Enum8:
                var enum8TypeInfo = (EnumType)databaseType;
                return(enum8TypeInfo.Lookup(reader.ReadSByte()));

            case ClickHouseTypeCode.Enum16:
                var enum16TypeInfo = (EnumType)databaseType;
                return(enum16TypeInfo.Lookup(reader.ReadInt16()));

            case ClickHouseTypeCode.LowCardinality:
                var lcCardinality = (LowCardinalityType)databaseType;
                return(ReadValue(lcCardinality.UnderlyingType, nullAsDbNull));
            }
            throw new NotImplementedException($"Reading of {databaseType.TypeCode} is not implemented");
        }
 public object Read(ClickHouseType type) => type.AcceptRead(this);
示例#12
0
 internal void Write(ClickHouseType clickHouseType, object value) => clickHouseType.AcceptWrite(this, value);
示例#13
0
        internal static string Format(ClickHouseType type, object value)
        {
            switch (type)
            {
            case IntegerType it:
                return(Convert.ToString(value, CultureInfo.InvariantCulture));

            case FloatType ft:
                return(FormatFloat(value));

            case DecimalType dt:
                return(FormatDecimal(dt, value));

            case LowCardinalityType lt:
                return(Format(lt.UnderlyingType, value));

            case StringType st:
            case FixedStringType tt:
            case Enum8Type e8t:
            case Enum16Type e16t:
                return(value.ToString().Escape());

            case UuidType ut:
                return($"toUUID({value.ToString().Escape()})");

            case NothingType nt:
                return("NULL");

            case DateType dt when value is DateTime dtv:
                return($"toDate('{dtv:yyyy-MM-dd}')");

            case DateTimeType dtt when value is DateTime dtv:
                return(dtt.TimeZone == null
                        ? $"toDateTime('{dtv:yyyy-MM-dd HH:mm:ss}')"
                        : $"toDateTime('{dtv:yyyy-MM-dd HH:mm:ss}', '{dtt.TimeZone.Id}')");

            case DateTimeType dtt when value is DateTimeOffset dto:
                return(dtt.TimeZone == null
                        ? $"toDateTime('{dto:yyyy-MM-dd HH:mm:ss}')"
                        : $"toDateTime('{dto:yyyy-MM-dd HH:mm:ss}', '{dtt.TimeZone.Id}')");

            case DateTime64Type dtt when value is DateTime dtv:
                var @string = dtt.ToZonedDateTime(dtv).ToString("yyyy-MM-dd HH:mm:ss.fffffff", CultureInfo.InvariantCulture);
                return(dtt.TimeZone != null
                        ? $"toDateTime64('{@string}', 7, '{dtt.TimeZone}')"
                        : $"toDateTime64('{@string}', 7)");

            case DateTime64Type dtt when value is DateTimeOffset dto:
                var @str2 = dtt.ToZonedDateTime(dto.DateTime).ToString("yyyy-MM-dd HH:mm:ss.fffffff", CultureInfo.InvariantCulture);
                return(dtt.TimeZone != null
                        ? $"toDateTime64('{@str2}', 7, '{dtt.TimeZone}')"
                        : $"toDateTime64('{@str2}', 7)");

            case IPv4Type it: return($"toIPv4({FormatIPAddress(value)})");

            case IPv6Type it: return($"toIPv6({FormatIPAddress(value)})");

            case NullableType nullableType:
                return(value is null || value is DBNull ? "null" : $"{Format(nullableType.UnderlyingType, value)}");

            case ArrayType arrayType when value is IEnumerable enumerable:
                var array = enumerable.Cast <object>().Select(obj => Format(arrayType.UnderlyingType, obj));
                return($"[{string.Join(",", array)}]");

            case TupleType tupleType when value is ITuple tuple:
                return($"({string.Join(",", tupleType.UnderlyingTypes.Select((x, i) => Format(x, tuple[i])))})");

            case MapType mapType when value is IDictionary dict:
                var strings = dict.Keys.Cast <object>().Select(k => $"{Format(mapType.KeyType, k)},{Format(mapType.ValueType, dict[k])}");
                return($"map({string.Join(",", strings)})");

            default:
                throw new NotSupportedException($"Cannot convert value {value} to ClickHouse type {type}");
            }
        }
        public void WriteValue(object data, ClickHouseType databaseType)
        {
            switch (databaseType.TypeCode)
            {
            case ClickHouseTypeCode.UInt8:
                writer.Write(Convert.ToByte(data));
                break;

            case ClickHouseTypeCode.UInt16:
                writer.Write(Convert.ToUInt16(data));
                break;

            case ClickHouseTypeCode.UInt32:
                writer.Write(Convert.ToUInt32(data));
                break;

            case ClickHouseTypeCode.UInt64:
                writer.Write(Convert.ToUInt64(data));
                break;

            case ClickHouseTypeCode.Int8:
                writer.Write(Convert.ToSByte(data));
                break;

            case ClickHouseTypeCode.Int16:
                writer.Write(Convert.ToInt16(data));
                break;

            case ClickHouseTypeCode.Int32:
                writer.Write(Convert.ToInt32(data));
                break;

            case ClickHouseTypeCode.Int64:
                writer.Write(Convert.ToInt64(data));
                break;

            case ClickHouseTypeCode.Float32:
                writer.Write(Convert.ToSingle(data));
                break;

            case ClickHouseTypeCode.Float64:
                writer.Write(Convert.ToDouble(data));
                break;

            case ClickHouseTypeCode.Decimal:
                var dti    = (DecimalType)databaseType;
                var value  = new BigInteger(MathUtils.ShiftDecimalPlaces(Convert.ToDecimal(data), dti.Scale));
                var dbytes = new byte[dti.Size];
                value.ToByteArray().CopyTo(dbytes, 0);
                writer.Write(dbytes);
                break;

            case ClickHouseTypeCode.String:
                writer.Write(Convert.ToString(data));
                break;

            case ClickHouseTypeCode.FixedString:
                var @string     = (string)data;
                var stringInfo  = (FixedStringType)databaseType;
                var stringBytes = new byte[stringInfo.Length];
                Encoding.UTF8.GetBytes(@string, 0, @string.Length, stringBytes, 0);

                writer.Write(stringBytes);
                break;

            case ClickHouseTypeCode.Array:
                var arrayTypeInfo = (ArrayType)databaseType;
                var collection    = (IList)data;
                writer.Write7BitEncodedInt(collection.Count);
                for (var i = 0; i < collection.Count; i++)
                {
                    WriteValue(collection[i], arrayTypeInfo.UnderlyingType);
                }

                break;

            case ClickHouseTypeCode.Nullable:
                var nullableTypeInfo = (NullableType)databaseType;
                if (data == null || data is DBNull)
                {
                    writer.Write((byte)1);
                }
                else
                {
                    writer.Write((byte)0);
                    WriteValue(data, nullableTypeInfo.UnderlyingType);
                }
                break;

            case ClickHouseTypeCode.Tuple:
                var tupleType = (TupleType)databaseType;
                var tuple     = (ITuple)data;
                for (var i = 0; i < tuple.Length; i++)
                {
                    WriteValue(tuple[i], tupleType.UnderlyingTypes[i]);
                }

                break;

            case ClickHouseTypeCode.Nested:
                var nestedType = (NestedType)databaseType;
                var tuples     = ((IEnumerable)data).Cast <ITuple>().ToList();
                writer.Write7BitEncodedInt(tuples.Count);
                foreach (var ntuple in tuples)
                {
                    for (int i = 0; i < ntuple.Length; i++)
                    {
                        WriteValue(ntuple[i], nestedType.UnderlyingTypes[i]);
                    }
                }

                break;

            case ClickHouseTypeCode.UUID:
                var guid  = ExtractGuid(data);
                var bytes = guid.ToByteArray();
                Array.Reverse(bytes, 8, 8);
                writer.Write(bytes, 6, 2);
                writer.Write(bytes, 4, 2);
                writer.Write(bytes, 0, 4);
                writer.Write(bytes, 8, 8);
                break;

            case ClickHouseTypeCode.IPv4:
                var address4 = ExtractIPAddress(data);
                if (address4.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork)
                {
                    throw new ArgumentException($"Expected IPv4, got {address4.ToString()}");
                }

                var ipv4bytes = address4.GetAddressBytes();
                Array.Reverse(ipv4bytes);
                writer.Write(ipv4bytes, 0, ipv4bytes.Length);
                break;

            case ClickHouseTypeCode.IPv6:
                var address6 = ExtractIPAddress(data);
                if (address6.AddressFamily != System.Net.Sockets.AddressFamily.InterNetworkV6)
                {
                    throw new ArgumentException($"Expected IPv4, got {address6.ToString()}");
                }

                var ipv6bytes = address6.GetAddressBytes();
                writer.Write(ipv6bytes, 0, ipv6bytes.Length);
                break;

            case ClickHouseTypeCode.Date:
                var days = (ushort)(((DateTime)data).Date - TypeConverter.DateTimeEpochStart).TotalDays;
                writer.Write(days);
                break;

            case ClickHouseTypeCode.DateTime:
                var dtType  = (DateTimeType)databaseType;
                var dto     = dtType.ToDateTimeOffset((DateTime)data);
                var seconds = (uint)(dto.UtcDateTime - TypeConverter.DateTimeEpochStart).TotalSeconds;
                writer.Write(seconds);
                break;

            case ClickHouseTypeCode.DateTime64:
                var dt64type = (DateTime64Type)databaseType;
                var dto64    = dt64type.ToDateTimeOffset((DateTime)data);
                var ticks    = (dto64.UtcDateTime - TypeConverter.DateTimeEpochStart).Ticks;
                // 7 is a 'magic constant' - Log10 of TimeSpan.TicksInSecond
                writer.Write(MathUtils.ShiftDecimalPlaces(ticks, dt64type.Scale - 7));
                break;

            case ClickHouseTypeCode.Nothing:
                break;

            case ClickHouseTypeCode.Enum8:
                var enum8TypeInfo = (EnumType)databaseType;
                var enum8Index    = data is string enum8Str ? (sbyte)enum8TypeInfo.Lookup(enum8Str) : Convert.ToSByte(data);
                writer.Write(enum8Index);
                break;

            case ClickHouseTypeCode.Enum16:
                var enum16TypeInfo = (EnumType)databaseType;
                var enum16Index    = data is string enum16Str ? (short)enum16TypeInfo.Lookup(enum16Str) : Convert.ToInt16(data);
                writer.Write(enum16Index);
                break;

            case ClickHouseTypeCode.LowCardinality:
                var lcCardinality = (LowCardinalityType)databaseType;
                WriteValue(data, lcCardinality.UnderlyingType);
                break;

            default:
                throw new NotImplementedException($"{databaseType.TypeCode} not supported yet");
            }
        }
        internal static string Format(ClickHouseType type, object value)
        {
            switch (type)
            {
            case NothingType nt:
                return(NullValueString);

            case IntegerType it:
            case FloatType ft:
                return(Convert.ToString(value, CultureInfo.InvariantCulture));

            case DecimalType dt:
                return(Convert.ToDecimal(value).ToString(CultureInfo.InvariantCulture));

            case DateType dt when value is DateTimeOffset @do:
                return(@do.Date.ToString("yyyy-MM-dd"));

            case DateType dt:
                return(Convert.ToDateTime(value).ToString("yyyy-MM-dd"));

            case StringType st:
            case FixedStringType tt:
            case Enum8Type e8t:
            case Enum16Type e16t:
            case IPv4Type ip4:
            case IPv6Type ip6:
            case UuidType uuidType:
                return(value.ToString());

            case LowCardinalityType lt:
                return(Format(lt.UnderlyingType, value));

            case DateTimeType dtt when value is DateTime dt:
                return(dt.ToString("s", CultureInfo.InvariantCulture));

            case DateTimeType dtt when value is DateTimeOffset dto:
                return(dto.ToString("s", CultureInfo.InvariantCulture));

            case DateTime64Type dtt when value is DateTime dtv:
                return($"{dtv:yyyy-MM-dd HH:mm:ss.fffffff}");

            case DateTime64Type dtt when value is DateTimeOffset dto:
                return($"{dto:yyyy-MM-dd HH:mm:ss.fffffff}");

            case NullableType nt:
                return(value is null || value is DBNull ? NullValueString : $"{Format(nt.UnderlyingType, value)}");

            case ArrayType arrayType when value is IEnumerable enumerable:
                return($"[{string.Join(",", enumerable.Cast<object>().Select(obj => InlineParameterFormatter.Format(arrayType.UnderlyingType, obj)))}]");

            case TupleType tupleType when value is ITuple tuple:
                return($"({string.Join(",", tupleType.UnderlyingTypes.Select((x, i) => InlineParameterFormatter.Format(x, tuple[i])))})");

            case MapType mapType when value is IDictionary dict:
                var strings = string.Join(",", dict.Keys.Cast <object>().Select(k => $"{InlineParameterFormatter.Format(mapType.KeyType, k)} : {InlineParameterFormatter.Format(mapType.ValueType, dict[k])}"));
                return($"{{{string.Join(",", strings)}}}");

            default:
                throw new Exception($"Cannot convert {value} to {type}");
            }
        }