Skip to content

Commit

Permalink
#4 Basic interface for enum converters and it's simple implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
victor-sushko committed Jul 24, 2020
1 parent 48eb736 commit 87c0033
Show file tree
Hide file tree
Showing 9 changed files with 315 additions and 13 deletions.
28 changes: 28 additions & 0 deletions src/Octonica.ClickHouseClient.Tests/TestEnum.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#region License Apache 2.0
/* Copyright 2020 Octonica
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion

namespace Octonica.ClickHouseClient.Tests
{
internal enum TestEnum
{
None = 0,
Value1 = 42,
Value2 = -134,
Value3 = 32000,
Value4 = int.MaxValue
}
}
13 changes: 13 additions & 0 deletions src/Octonica.ClickHouseClient.Tests/TypeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
using System.Threading.Tasks;
using Octonica.ClickHouseClient.Exceptions;
using Octonica.ClickHouseClient.Protocol;
using Octonica.ClickHouseClient.Types;
using TimeZoneConverter;
using Xunit;

Expand Down Expand Up @@ -396,6 +397,18 @@ public async Task ReadEnumScalar()
Assert.Equal("Hello, world! :)", result);
}

[Fact]
public async Task ReadClrEnumScalar()
{
await using var connection = await OpenConnectionAsync();

await using var cmd = connection.CreateCommand("SELECT CAST(42 AS Enum('' = 0, 'b' = -129, 'Hello, world! :)' = 42))");

var settings = new ClickHouseColumnSettings(new ClickHouseEnumConverter<TestEnum>());
var result = await cmd.ExecuteScalarAsync(settings);
Assert.Equal(TestEnum.Value1, result);
}

[Fact]
public async Task ReadInt32ArrayColumn()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,30 @@

using System;
using System.Text;
using Octonica.ClickHouseClient.Types;

namespace Octonica.ClickHouseClient.Protocol
{
public class ClickHouseColumnSettings
{
public Encoding StringEncoding { get; }
public Encoding? StringEncoding { get; }

public IClickHouseEnumConverter? EnumConverter { get; }

public ClickHouseColumnSettings(Encoding stringEncoding)
{
StringEncoding = stringEncoding ?? throw new ArgumentNullException(nameof(stringEncoding));
}

public ClickHouseColumnSettings(IClickHouseEnumConverter enumConverter)
{
EnumConverter = enumConverter ?? throw new ArgumentNullException(nameof(enumConverter));
}

public ClickHouseColumnSettings(Encoding? stringEncoding = null, IClickHouseEnumConverter? enumConverter = null)
{
StringEncoding = stringEncoding;
EnumConverter = enumConverter;
}
}
}
49 changes: 49 additions & 0 deletions src/Octonica.ClickHouseClient/Types/ClickHouseEnumConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#region License Apache 2.0
/* Copyright 2020 Octonica
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion

using System;
using System.Collections.Generic;

namespace Octonica.ClickHouseClient.Types
{
public sealed class ClickHouseEnumConverter<TEnum> : IClickHouseEnumConverter<TEnum>
where TEnum : Enum
{
private readonly Dictionary<int, TEnum> _values;

public ClickHouseEnumConverter()
{
var enumValues = Enum.GetValues(typeof(TEnum));
_values = new Dictionary<int, TEnum>(enumValues.Length);
foreach (var enumValue in enumValues)
{
var intValue = Convert.ToInt32(enumValue);
_values[intValue] = (TEnum) enumValue!;
}
}

T IClickHouseEnumConverter.Dispatch<T>(IClickHouseEnumConverterDispatcher<T> dispatcher)
{
return dispatcher.Dispatch(this);
}

public bool TryMap(int value, string stringValue, out TEnum enumValue)
{
return _values.TryGetValue(value, out enumValue!);
}
}
}
31 changes: 31 additions & 0 deletions src/Octonica.ClickHouseClient/Types/Enum16TypeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ private Enum16TypeInfo(string typeName, string complexTypeName, IEnumerable<KeyV
{
}

protected override EnumColumnReaderBase CreateColumnReader(StructureReaderBase<short> internalReader, IReadOnlyDictionary<short, string> reversedEnumMap)
{
return new EnumColumnReader(internalReader, reversedEnumMap);
}

protected override IClickHouseColumnTypeInfo CreateDetailedTypeInfo(string complexTypeName, IEnumerable<KeyValuePair<string, short>> values)
{
return new Enum16TypeInfo(TypeName, complexTypeName, values);
Expand Down Expand Up @@ -64,5 +69,31 @@ protected override bool TryParse(ReadOnlySpan<char> text, out short value)
{
return short.TryParse(text, out value);
}

private sealed class EnumColumnReader : EnumColumnReaderBase
{
public EnumColumnReader(StructureReaderBase<short> internalReader, IReadOnlyDictionary<short, string> reversedEnumMap)
: base(internalReader, reversedEnumMap)
{
}

protected override EnumTableColumnDispatcherBase CreateColumnDispatcher(IClickHouseTableColumn<short> column, IReadOnlyDictionary<short, string> reversedEnumMap)
{
return new EnumTableColumnDispatcher(column, reversedEnumMap);
}
}

private sealed class EnumTableColumnDispatcher : EnumTableColumnDispatcherBase
{
public EnumTableColumnDispatcher(IClickHouseTableColumn<short> column, IReadOnlyDictionary<short, string> reversedEnumMap)
: base(column, reversedEnumMap)
{
}

protected override bool TryMap<TEnum>(IClickHouseEnumConverter<TEnum> enumConverter, short value, string stringValue, out TEnum enumValue)
{
return enumConverter.TryMap(value, stringValue, out enumValue);
}
}
}
}
31 changes: 31 additions & 0 deletions src/Octonica.ClickHouseClient/Types/Enum8TypeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ private Enum8TypeInfo(string typeName, string complexTypeName, IEnumerable<KeyVa
{
}

protected override EnumColumnReaderBase CreateColumnReader(StructureReaderBase<sbyte> internalReader, IReadOnlyDictionary<sbyte, string> reversedEnumMap)
{
return new EnumColumnReader(internalReader, reversedEnumMap);
}

protected override IClickHouseColumnTypeInfo CreateDetailedTypeInfo(string complexTypeName, IEnumerable<KeyValuePair<string, sbyte>> values)
{
return new Enum8TypeInfo(TypeName, complexTypeName, values);
Expand All @@ -56,5 +61,31 @@ protected override bool TryParse(ReadOnlySpan<char> text, out sbyte value)
{
return sbyte.TryParse(text, out value);
}

private sealed class EnumColumnReader : EnumColumnReaderBase
{
public EnumColumnReader(StructureReaderBase<sbyte> internalReader, IReadOnlyDictionary<sbyte, string> reversedEnumMap)
: base(internalReader, reversedEnumMap)
{
}

protected override EnumTableColumnDispatcherBase CreateColumnDispatcher(IClickHouseTableColumn<sbyte> column, IReadOnlyDictionary<sbyte, string> reversedEnumMap)
{
return new EnumTableColumnDispatcher(column, reversedEnumMap);
}
}

private sealed class EnumTableColumnDispatcher : EnumTableColumnDispatcherBase
{
public EnumTableColumnDispatcher(IClickHouseTableColumn<sbyte> column, IReadOnlyDictionary<sbyte, string> reversedEnumMap)
: base(column, reversedEnumMap)
{
}

protected override bool TryMap<TEnum>(IClickHouseEnumConverter<TEnum> enumConverter, sbyte value, string stringValue, out TEnum enumValue)
{
return enumConverter.TryMap(value, stringValue, out enumValue);
}
}
}
}
71 changes: 64 additions & 7 deletions src/Octonica.ClickHouseClient/Types/EnumTableColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@

namespace Octonica.ClickHouseClient.Types
{
internal sealed class EnumTableColumn<TValue> : IClickHouseTableColumn<string>
where TValue : struct
internal sealed class EnumTableColumn<TKey> : IClickHouseTableColumn<string>
where TKey : struct
{
private readonly IClickHouseTableColumn<TValue> _internalColumn;
private readonly IReadOnlyDictionary<TValue, string> _reversedEnumMap;
private readonly IClickHouseTableColumn<TKey> _internalColumn;
private readonly IReadOnlyDictionary<TKey, string> _valueMap;

public int RowCount => _internalColumn.RowCount;

public EnumTableColumn(IClickHouseTableColumn<TValue> internalColumn, IReadOnlyDictionary<TValue, string> reversedEnumMap)
public EnumTableColumn(IClickHouseTableColumn<TKey> internalColumn, IReadOnlyDictionary<TKey, string> valueMap)
{
_internalColumn = internalColumn;
_reversedEnumMap = reversedEnumMap;
_valueMap = valueMap;
}

public bool IsNull(int index)
Expand All @@ -44,7 +44,7 @@ public bool IsNull(int index)
public string GetValue(int index)
{
var value = _internalColumn.GetValue(index);
if (!_reversedEnumMap.TryGetValue(value, out var strValue))
if (!_valueMap.TryGetValue(value, out var strValue))
throw new InvalidCastException($"There is no string representation for the value {value} in the enum.");

return strValue;
Expand All @@ -64,4 +64,61 @@ object IClickHouseTableColumn.GetValue(int index)
return null;
}
}

internal sealed class EnumTableColumn<TKey, TEnum> : IClickHouseTableColumn<TEnum>
where TKey : struct
where TEnum : Enum
{
private readonly IClickHouseTableColumn<TKey> _internalColumn;
private readonly IReadOnlyDictionary<TKey, TEnum> _enumMap;
private readonly IReadOnlyDictionary<TKey, string> _stringMap;

public int RowCount => _internalColumn.RowCount;

public EnumTableColumn(IClickHouseTableColumn<TKey> internalColumn, IReadOnlyDictionary<TKey, TEnum> enumMap, IReadOnlyDictionary<TKey, string> stringMap)
{
_internalColumn = internalColumn;
_enumMap = enumMap;
_stringMap = stringMap;
}

public bool IsNull(int index)
{
Debug.Assert(!_internalColumn.IsNull(index));
return false;
}

public TEnum GetValue(int index)
{
var value = _internalColumn.GetValue(index);
if (!_enumMap.TryGetValue(value, out var strValue))
{
if (_stringMap.TryGetValue(value, out var nativeValue))
throw new InvalidCastException($"The value '{nativeValue}'={value} of the enum can't be converted to the type '{typeof(Enum).FullName}'.");

throw new InvalidCastException($"The value {value} doesn't belong to the enum.");
}

return strValue;
}

object IClickHouseTableColumn.GetValue(int index)
{
return GetValue(index);
}

public IClickHouseTableColumn<T>? TryReinterpret<T>()
{
IClickHouseTableColumn<T>? reinterpretedColumn;
if (typeof(T) == typeof(string))
reinterpretedColumn = (IClickHouseTableColumn<T>) (object) new EnumTableColumn<TKey>(_internalColumn, _stringMap);
else
reinterpretedColumn = _internalColumn.TryReinterpret<T>();

if (reinterpretedColumn == null)
return null;

return new ReinterpretedTableColumn<T>(this, reinterpretedColumn);
}
}
}
Loading

0 comments on commit 87c0033

Please sign in to comment.