From 59ffcd8451d82c991f795585ed48ffeabf246a4b Mon Sep 17 00:00:00 2001 From: Victor Sushko Date: Thu, 13 May 2021 19:00:56 +0500 Subject: [PATCH] #4 Add support for escape sequences in enum's item names. --- .../ClickHouseTypeInfoTests.cs | 4 +- .../TypeTests.cs | 58 +++++++++++++++++++ .../Types/EnumTypeInfoBase.cs | 24 ++++---- 3 files changed, 73 insertions(+), 13 deletions(-) diff --git a/src/Octonica.ClickHouseClient.Tests/ClickHouseTypeInfoTests.cs b/src/Octonica.ClickHouseClient.Tests/ClickHouseTypeInfoTests.cs index b9dac0c..0e438b1 100644 --- a/src/Octonica.ClickHouseClient.Tests/ClickHouseTypeInfoTests.cs +++ b/src/Octonica.ClickHouseClient.Tests/ClickHouseTypeInfoTests.cs @@ -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); @@ -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); @@ -207,7 +209,7 @@ public void Enum16TypeArguments(string typeName, string[] expectedKeys, short[] var typeArgument = Assert.IsType>(typeArgumentObj); Assert.Equal(typeArgument.Key, expectedKeys[i]); Assert.Equal(typeArgument.Value, expectedValues[i]); - } + } } [Fact] diff --git a/src/Octonica.ClickHouseClient.Tests/TypeTests.cs b/src/Octonica.ClickHouseClient.Tests/TypeTests.cs index fd39b4f..6f6861c 100644 --- a/src/Octonica.ClickHouseClient.Tests/TypeTests.cs +++ b/src/Octonica.ClickHouseClient.Tests/TypeTests.cs @@ -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(); + for(int i=0; i>(obj); + typeNames.Add(pair.Value, pair.Key); + } + + int bitmap = 0; + while (await reader.ReadAsync()) + { + var value = reader.GetFieldValue(0); + var defaultValue = reader.GetValue(0); + var strValue = Assert.IsType(defaultValue); + var expectedStrValue = reader.GetFieldValue(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() { diff --git a/src/Octonica.ClickHouseClient/Types/EnumTypeInfoBase.cs b/src/Octonica.ClickHouseClient/Types/EnumTypeInfoBase.cs index 8a61671..88b68f7 100644 --- a/src/Octonica.ClickHouseClient/Types/EnumTypeInfoBase.cs +++ b/src/Octonica.ClickHouseClient/Types/EnumTypeInfoBase.cs @@ -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; @@ -29,8 +28,6 @@ namespace Octonica.ClickHouseClient.Types internal abstract class EnumTypeInfoBase : IClickHouseColumnTypeInfo where TValue : struct { - private readonly Regex _enumItemRegex = new Regex(@"^\s*\'([^']*)\'\s*=\s*(-?\d+)\s*$"); - private readonly Dictionary? _enumMap; private readonly Dictionary? _reversedEnumMap; private readonly List? _mapOrder; @@ -131,18 +128,21 @@ public IClickHouseColumnTypeInfo GetDetailedTypeInfo(List> 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(key, value)); }