Skip to content

Commit

Permalink
#4 Enum8 and Enum16 types
Browse files Browse the repository at this point in the history
  • Loading branch information
victor-sushko committed Jun 2, 2020
1 parent 1257cff commit 927fae2
Show file tree
Hide file tree
Showing 11 changed files with 535 additions and 85 deletions.
51 changes: 51 additions & 0 deletions src/Octonica.ClickHouseClient.Tests/ColumnWriterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,57 @@ public async Task InsertLowCardinalityValues()
static string? NumToString(int num) => num % 15 == 0 ? null : num % 3 == 0 ? "foo" : num % 5 == 0 ? "bar" : num % 2 == 0 ? "true" : "false";
}

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

var cmd = connection.CreateCommand($"DROP TABLE IF EXISTS {TestTableName}_enums");
await cmd.ExecuteNonQueryAsync();

cmd = connection.CreateCommand($"CREATE TABLE {TestTableName}_enums(id Int16, e8 Enum8('min' = -128, 'zero' = 0, 'max' = 127), e16 Enum16('unknown value' = 0, 'well known value' = 42, 'foo' = -1024, 'bar' = 108)) ENGINE=Memory");
await cmd.ExecuteNonQueryAsync();

await using (var writer = connection.CreateColumnWriter($"INSERT INTO {TestTableName}_enums(id, e16, e8) VALUES"))
{
var source = new object[]
{
Enumerable.Range(0, 1000).Select(num => (short) num),
Enumerable.Range(-500, 1000).Select(num => num % 108 == 0 ? "bar" : num < 0 ? "foo" : num == 42 ? "well known value" : "unknown value"),
Enumerable.Range(0, 1000).Select(num => num % 3 == 0 ? sbyte.MinValue : num % 3 == 1 ? (sbyte) 0 : sbyte.MaxValue)
};

await writer.WriteTableAsync(source, 1000, CancellationToken.None);
await writer.EndWriteAsync(CancellationToken.None);
}

cmd.CommandText = $"SELECT e8, e16 FROM {TestTableName}_enums ORDER BY id";

int count = 0;
await using var reader = await cmd.ExecuteReaderAsync();

while (reader.Read())
{
var e8 = reader.GetValue(0);
var e16 = reader.GetInt16(1);

Assert.Equal(count % 3 == 0 ? "min" : count % 3 == 1 ? "zero" : "max", e8);
Assert.Equal((count - 500) % 108 == 0 ? 108 : count - 500 < 0 ? -1024 : count - 500 == 42 ? 42 : 0, e16);
++count;
}

Assert.Equal(1000, count);
}
finally
{
await using var connection = await OpenConnectionAsync();
var cmd = connection.CreateCommand($"DROP TABLE IF EXISTS {TestTableName}_enums");
await cmd.ExecuteNonQueryAsync();
}
}

[Fact]
public async Task InsertLargeTable()
{
Expand Down
141 changes: 78 additions & 63 deletions src/Octonica.ClickHouseClient.Tests/TypeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,17 @@ public async Task ReadNullableNothingArrayScalar()
Assert.Equal(new object[3], resultArr);
}

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

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

var result = await cmd.ExecuteScalarAsync();
Assert.Equal("Hello, world! :)", result);
}

[Fact]
public async Task ReadInt32ArrayColumn()
{
Expand Down Expand Up @@ -1463,79 +1474,79 @@ public async Task CreateInsertSelectAllKnownNullable()
{
const string ddl = @"
CREATE TABLE clickhouse_test_nullable (
int8 Nullable(Int8),
int16 Nullable(Int16),
int32 Nullable(Int32),
int64 Nullable(Int64),
uint8 Nullable(UInt8),
uint16 Nullable(UInt16),
uint32 Nullable(UInt32),
uint64 Nullable(UInt64),
float32 Nullable(Float32),
float64 Nullable(Float64),
string Nullable(String),
fString Nullable(FixedString(2)),
date Nullable(Date),
datetime Nullable(DateTime),
enum8 Nullable(Enum8 ('a' = 1, 'b' = 2)),
enum16 Nullable(Enum16('c' = 1, 'd' = 2))
int8 Nullable(Int8),
int16 Nullable(Int16),
int32 Nullable(Int32),
int64 Nullable(Int64),
uint8 Nullable(UInt8),
uint16 Nullable(UInt16),
uint32 Nullable(UInt32),
uint64 Nullable(UInt64),
float32 Nullable(Float32),
float64 Nullable(Float64),
string Nullable(String),
fString Nullable(FixedString(2)),
date Nullable(Date),
datetime Nullable(DateTime),
enum8 Nullable(Enum8 ('a' = 127, 'b' = 2)),
enum16 Nullable(Enum16('c' = -32768, 'd' = 2, '' = 42))
) Engine=Memory;";

const string dml = @"
INSERT INTO clickhouse_test_nullable (
int8
,int16
,int32
,int64
,uint8
,uint16
,uint32
,uint64
,float32
,float64
,string
,fString
,date
,datetime
//,enum8
//,enum16
int8
,int16
,int32
,int64
,uint8
,uint16
,uint32
,uint64
,float32
,float64
,string
,fString
,date
,datetime
,enum8
,enum16
) VALUES (
8
,16
,32
,64
,18
,116
,132
,165
,1.1
,1.2
,'RU'
,'UA'
,now()
,now()
// ,'a'
// ,'c'
,16
,32
,64
,18
,116
,132
,165
,1.1
,1.2
,'RU'
,'UA'
,now()
,now()
,'a'
,'c'
)";

const string query = @"
SELECT
int8
,int16
,int32
,int64
,uint8
,uint16
,uint32
,uint64
,float32
,float64
,string
,fString
,date
,datetime
// ,enum8
// enum16
int8
,int16
,int32
,int64
,uint8
,uint16
,uint32
,uint64
,float32
,float64
,string
,fString
,date
,datetime
,enum8
,enum16
FROM clickhouse_test_nullable";

try
Expand Down Expand Up @@ -1574,6 +1585,10 @@ INSERT INTO clickhouse_test_nullable (
Assert.Equal(fixedStringBytes, fixedStringBytesAsValue);
Assert.NotNull(fixedStringBytes as byte[]);
Assert.Equal("UA", Encoding.Default.GetString(fixedStringBytes as byte[]));
Assert.Equal("a", r.GetValue(14));
Assert.Equal(127, r.GetFieldValue<sbyte>(14));
Assert.Equal("c", r.GetValue(15));
Assert.Equal(-32768, r.GetInt16(15));
}
}
finally
Expand Down
2 changes: 2 additions & 0 deletions src/Octonica.ClickHouseClient/ClickHouseDbType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,7 @@ public enum ClickHouseDbType
Tuple = ClickHouseSpecificTypeDelimiterCode + 4,

Nothing = ClickHouseSpecificTypeDelimiterCode + 5,

Enum = ClickHouseSpecificTypeDelimiterCode + 6,
}
}
45 changes: 28 additions & 17 deletions src/Octonica.ClickHouseClient/Types/DefaultTypeInfoProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ private static (ReadOnlyMemory<char> baseTypeName, List<ReadOnlyMemory<char>>? o
int count = 1;
int currentIdx = pOpenIdx;
int optionStartIdx = pOpenIdx + 1;
ReadOnlySpan<char> significantChars = "(,)";
bool escaped = false;
ReadOnlySpan<char> significantChars = "(,)'";
do
{
if (typeNameSpan.Length - 1 == currentIdx)
Expand All @@ -103,25 +104,32 @@ private static (ReadOnlyMemory<char> baseTypeName, List<ReadOnlyMemory<char>>? o

pNextIdx += currentIdx + 1;
currentIdx = pNextIdx;
if (typeNameSpan[currentIdx] == '(')
if (typeNameSpan[currentIdx] == '\'')
{
++count;
escaped = !escaped;
}
else if (typeNameSpan[currentIdx] == ')')
else if (!escaped)
{
--count;
if (count == 0)
break;
}
else if (count == 1)
{
var currentOption = typeName.Slice(optionStartIdx, currentIdx - optionStartIdx).Trim();
optionStartIdx = currentIdx + 1;

if (options != null)
options.Add(currentOption);
else
options = new List<ReadOnlyMemory<char>>(2) {currentOption};
if (typeNameSpan[currentIdx] == '(')
{
++count;
}
else if (typeNameSpan[currentIdx] == ')')
{
--count;
if (count == 0)
break;
}
else if (count == 1)
{
var currentOption = typeName.Slice(optionStartIdx, currentIdx - optionStartIdx).Trim();
optionStartIdx = currentIdx + 1;

if (options != null)
options.Add(currentOption);
else
options = new List<ReadOnlyMemory<char>>(2) {currentOption};
}
}

} while (true);
Expand Down Expand Up @@ -197,6 +205,9 @@ protected static IEnumerable<IClickHouseColumnTypeInfo> GetDefaultTypes()

new IpV4TypeInfo(),
new IpV6TypeInfo(),

new Enum8TypeInfo(),
new Enum16TypeInfo(),
};
}
}
Expand Down
68 changes: 68 additions & 0 deletions src/Octonica.ClickHouseClient/Types/Enum16TypeInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#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;
using Octonica.ClickHouseClient.Exceptions;
using Octonica.ClickHouseClient.Protocol;
using Octonica.ClickHouseClient.Utils;

namespace Octonica.ClickHouseClient.Types
{
internal sealed class Enum16TypeInfo : EnumTypeInfoBase<short>
{
public Enum16TypeInfo()
: base("Enum16")
{
}

private Enum16TypeInfo(string typeName, string complexTypeName, IEnumerable<KeyValuePair<string, short>> values)
: base(typeName, complexTypeName, values)
{
}

protected override IClickHouseColumnTypeInfo CreateDetailedTypeInfo(string complexTypeName, IEnumerable<KeyValuePair<string, short>> values)
{
return new Enum16TypeInfo(TypeName, complexTypeName, values);
}

protected override StructureReaderBase<short> CreateInternalColumnReader(int rowCount)
{
return new Int16TypeInfo.Int16Reader(rowCount);
}

protected override IClickHouseColumnWriter CreateInternalColumnWriter<T>(string columnName, IReadOnlyList<T> rows)
{
if (!(rows is IReadOnlyList<short> shortRows))
{
if (rows is IReadOnlyList<byte> byteRows)
shortRows = new MappedReadOnlyList<byte, short>(byteRows, v => v);
else if (rows is IReadOnlyList<sbyte> sbyteRows)
shortRows = new MappedReadOnlyList<sbyte, short>(sbyteRows, v => v);
else
throw new ClickHouseException(ClickHouseErrorCodes.TypeNotSupported, $"The type \"{typeof(T)}\" can't be converted to the ClickHouse type \"{TypeName}\".");
}

return new Int16TypeInfo.Int16Writer(columnName, ComplexTypeName, shortRows);
}

protected override bool TryParse(ReadOnlySpan<char> text, out short value)
{
return short.TryParse(text, out value);
}
}
}
Loading

0 comments on commit 927fae2

Please sign in to comment.