Skip to content

Commit

Permalink
#4 Add support for escape sequences in enum's item names.
Browse files Browse the repository at this point in the history
  • Loading branch information
victor-sushko committed May 13, 2021
1 parent 32735fb commit 59ffcd8
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ public void DateTime64TypeArguments()
[Theory]
[InlineData("Enum8('a' = 42)", new[] { "a" }, new sbyte[] { 42 })]
[InlineData("Enum8('a' = 2, 'C' = -3,'b'=1)", new[] { "a", "C", "b" }, new sbyte[] { 2, -3, 1 })]
[InlineData("Enum8('\\'a\\'' = -5, ' \\tescaped \\'value\\' ({[ ' = -9,'\\r\\n\\t\\d\\\\'= 18)", new[] { "'a'", " \tescaped 'value' ({[ ", "\r\n\t\\d\\" }, new sbyte[] { -5, -9, 18 })]
public void Enum8TypeArguments(string typeName, string[] expectedKeys, sbyte[] expectedValues)
{
Assert.Equal(expectedKeys.Length, expectedValues.Length);
Expand All @@ -191,6 +192,7 @@ public void Enum8TypeArguments(string typeName, string[] expectedKeys, sbyte[] e
[Theory]
[InlineData("Enum16('a' = 1024)", new[] { "a" }, new short[] { 1024 })]
[InlineData("Enum16('a' = 8965, 'C' = 5,'b'=-3256)", new[] { "a", "C", "b" }, new short[] { 8965, 5, -3256 })]
[InlineData("Enum16('\"a\"' = 31000 , '\\'\\\\e\\s\\c\\\\a\\p\\e\\d\\'' = -31000, '}])' = 42)", new[] { "\"a\"", @"'\e\s\c\a\p" + "\x1b" + @"\d'", "}])" }, new short[] { 31000, -31000, 42 })]
public void Enum16TypeArguments(string typeName, string[] expectedKeys, short[] expectedValues)
{
Assert.Equal(expectedKeys.Length, expectedValues.Length);
Expand All @@ -207,7 +209,7 @@ public void Enum16TypeArguments(string typeName, string[] expectedKeys, short[]
var typeArgument = Assert.IsType<KeyValuePair<string, short>>(typeArgumentObj);
Assert.Equal(typeArgument.Key, expectedKeys[i]);
Assert.Equal(typeArgument.Value, expectedValues[i]);
}
}
}

[Fact]
Expand Down
58 changes: 58 additions & 0 deletions src/Octonica.ClickHouseClient.Tests/TypeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,64 @@ public async Task ReadMultidimensionalArrayParameterScalar()
}
}

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

await using var cmd = connection.CreateCommand(@"SELECT CAST(T.col AS Enum(''=0, '\\e\s\c\\a\p\\e'=1, '\'val\''=2, '\r\n\t\d\\\r\n'=3,'\a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x20\y\z'=4)) AS enumVal, toString(enumVal) AS strVal
FROM (SELECT 0 AS col UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4) AS T");

await using var reader = await cmd.ExecuteReaderAsync();
var columnType = reader.GetFieldTypeInfo(0);
var typeNames = new Dictionary<int, string>();
for(int i=0; i<columnType.TypeArgumentsCount; i++)
{
var obj = columnType.GetTypeArgument(i);
var pair = Assert.IsType<KeyValuePair<string, sbyte>>(obj);
typeNames.Add(pair.Value, pair.Key);
}

int bitmap = 0;
while (await reader.ReadAsync())
{
var value = reader.GetFieldValue<int>(0);
var defaultValue = reader.GetValue(0);
var strValue = Assert.IsType<string>(defaultValue);
var expectedStrValue = reader.GetFieldValue<string>(1);

Assert.Equal(expectedStrValue, strValue);

switch (value)
{
case 0:
Assert.Equal(string.Empty, strValue);
break;
case 1:
Assert.Equal(@"\e\s\c\a\p\e", strValue);
break;
case 2:
Assert.Equal("'val'", strValue);
break;
case 3:
Assert.Equal("\r\n\t\\d\\\r\n", strValue);
break;
case 4:
Assert.Equal("\a\b\\c\\d\u001b\f\\g\\h\\i\\j\\k\\l\\m\n\\o\\p\\q\r\\s\t\\u\v\\w\x20\\y\\z", strValue);
break;
default:
Assert.True(false, $"Unexpected value: {value}.");
break;
}

Assert.Equal(strValue, typeNames[value]);

bitmap ^= 1 << value;
}

Assert.Equal(31, bitmap);
}

[Fact]
public async Task ReadEnumScalar()
{
Expand Down
24 changes: 12 additions & 12 deletions src/Octonica.ClickHouseClient/Types/EnumTypeInfoBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
using System.Buffers;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Octonica.ClickHouseClient.Exceptions;
using Octonica.ClickHouseClient.Protocol;
using Octonica.ClickHouseClient.Utils;
Expand All @@ -29,8 +28,6 @@ namespace Octonica.ClickHouseClient.Types
internal abstract class EnumTypeInfoBase<TValue> : IClickHouseColumnTypeInfo
where TValue : struct
{
private readonly Regex _enumItemRegex = new Regex(@"^\s*\'([^']*)\'\s*=\s*(-?\d+)\s*$");

private readonly Dictionary<string, TValue>? _enumMap;
private readonly Dictionary<TValue, string>? _reversedEnumMap;
private readonly List<string>? _mapOrder;
Expand Down Expand Up @@ -131,18 +128,21 @@ public IClickHouseColumnTypeInfo GetDetailedTypeInfo(List<ReadOnlyMemory<char>>
else
complexNameBuilder.Append(", ");

var optionStr = option.ToString();
var match = _enumItemRegex.Match(optionStr);
if (!match.Success)
throw new ClickHouseException(ClickHouseErrorCodes.InvalidTypeName, $"The fragment \"{optionStr}\" is not recognized as an item of the enum.");
var keyStrLen = ClickHouseSyntaxHelper.GetSingleQuoteStringLength(option.Span);
if (keyStrLen < 0)
throw new ClickHouseException(ClickHouseErrorCodes.InvalidTypeName, $"The fragment \"{option}\" is not recognized as an item of the enum.");

var key = match.Groups[1].Value;
var valueStr = match.Groups[2].Value;
var key = ClickHouseSyntaxHelper.GetSingleQuoteString(option.Slice(0, keyStrLen).Span);
var valuePart = option.Slice(keyStrLen);
var eqSignIdx = valuePart.Span.IndexOf('=');
if (eqSignIdx < 0)
throw new ClickHouseException(ClickHouseErrorCodes.InvalidTypeName, $"The fragment \"{option}\" is not recognized as an item of the enum.");

if (!TryParse(valueStr, out var value))
throw new ClickHouseException(ClickHouseErrorCodes.InvalidTypeName, $"The value {valueStr} is not a valid value of {TypeName}.");
valuePart = valuePart.Slice(eqSignIdx + 1).Trim();
if (!TryParse(valuePart.Span, out var value))
throw new ClickHouseException(ClickHouseErrorCodes.InvalidTypeName, $"The value {valuePart} is not a valid value of {TypeName}.");

complexNameBuilder.Append(optionStr);
complexNameBuilder.Append(option);
parsedOptions.Add(new KeyValuePair<string, TValue>(key, value));
}

Expand Down

0 comments on commit 59ffcd8

Please sign in to comment.