Skip to content

Commit

Permalink
Support for alias types (#257)
Browse files Browse the repository at this point in the history
  • Loading branch information
DarkWanderer authored Dec 21, 2022
1 parent 645f647 commit 91093b8
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 5 deletions.
14 changes: 13 additions & 1 deletion ClickHouse.Client.Tests/TestUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ public static ClickHouseConnection GetTestClickHouseConnection(bool compression
{
builder["set_allow_experimental_geo_types"] = 1; // Allow support for experimental geo types
}
if (SupportedFeatures.HasFlag(Feature.Json))
{
builder["set_allow_experimental_object_type"] = 1; // Allow support for JSON
builder["set_output_format_json_named_tuples_as_objects"] = 1;
}
return new ClickHouseConnection(builder.ConnectionString);
}

Expand Down Expand Up @@ -158,7 +163,9 @@ public static IEnumerable<DataTypeSample> GetDataTypeSamples()
// Code: 53. DB::Exception: Type mismatch in IN or VALUES section. Expected: Decimal(76, 25). Got: Decimal256:
// While processing toDecimal256(1e-24, 25) AS expected, _CAST('0.000000000000000000000001', 'Decimal256(25)') AS actual, expected = actual AS equals. (TYPE_MISMATCH) (version 22.9.3.18 (official build))
//yield return new DataTypeSample("Decimal256(25)", typeof(ClickHouseDecimal), "toDecimal256(1e-24, 25)", new ClickHouseDecimal(10e-25m));
//yield return new DataTypeSample("Decimal256(0)", typeof(ClickHouseDecimal), "toDecimal256(repeat('1', 50), 0)", ClickHouseDecimal.Parse(new string('1', 50)));
//yield return new DataTypeSample("Decimal256(0)", typeof(ClickHouseDecimal),
//
//"toDecimal256(repeat('1', 50), 0)", ClickHouseDecimal.Parse(new string('1', 50)));
}

if (SupportedFeatures.HasFlag(Feature.IPv6))
Expand Down Expand Up @@ -211,6 +218,11 @@ public static IEnumerable<DataTypeSample> GetDataTypeSamples()
Tuple.Create(.3, .4)
});
}

if (SupportedFeatures.HasFlag(Feature.Json))
{
//yield return new DataTypeSample("Json", typeof(string), "'{\"a\": \"b\", \"c\": 3}'", "{\"a\": \"b\", \"c\": 3}");
}
}

public static object[] GetEnsureSingleRow(this DbDataReader reader)
Expand Down
22 changes: 22 additions & 0 deletions ClickHouse.Client.Tests/Types/TypeMappingTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using ClickHouse.Client.Numerics;
using ClickHouse.Client.Types;
using Dapper;
using NUnit.Framework;
namespace ClickHouse.Client.Tests;

Expand Down Expand Up @@ -79,4 +82,23 @@ public class TypeMappingTests
[TestCase(typeof(string[][]), ExpectedResult = "Array(Array(String))")]
[TestCase(typeof(Tuple<int, byte, float?, string[]>), ExpectedResult = "Tuple(Int32,UInt8,Nullable(Float32),Array(String))")]
public string ShouldConvertToClickHouseType(Type type) => TypeConverter.ToClickHouseType(type).ToString();

[Test, Explicit, TestCaseSource(nameof(GetClickHouseRegisteredTypes))]
public void ShouldConvertClickHouseType(string clickHouseType)
{
try
{
TypeConverter.ParseClickHouseType(clickHouseType, TypeSettings.Default);
}
catch (ArgumentOutOfRangeException)
{
Assert.Pass("Expected failure (no arguments provided");
}
}

private static IList<string> GetClickHouseRegisteredTypes()
{
using var connection = TestUtilities.GetTestClickHouseConnection();
return connection.Query<string>("SELECT name FROM system.data_type_families") as IList<string>;
}
}
4 changes: 4 additions & 0 deletions ClickHouse.Client/ADO/ClickHouseConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,10 @@ internal static Feature GetFeatureFlags(Version serverVersion)
{
flags |= Feature.Stats;
}
if (serverVersion >= new Version(22, 6))
{
flags |= Feature.Json;
}

return flags;
}
Expand Down
1 change: 1 addition & 0 deletions ClickHouse.Client/ADO/Feature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public enum Feature
WideTypes = 512,
Geo = 1024,
Stats = 2048,
Json = 4096,

All = ~None, // Special value
}
1 change: 1 addition & 0 deletions ClickHouse.Client/Formats/HttpParameterFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ internal static string Format(ClickHouseType type, object value, bool quote)
case IPv4Type ip4:
case IPv6Type ip6:
case UuidType uuidType:
case JsonType jsonType:
return quote ? value.ToString().Escape().QuoteSingle() : value.ToString().Escape();

case LowCardinalityType lt:
Expand Down
4 changes: 1 addition & 3 deletions ClickHouse.Client/Types/ArrayType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,12 @@ public override ParameterizedType Parse(SyntaxTreeNode node, Func<SyntaxTreeNode
};
}

public Array MakeArray(int length) => Array.CreateInstance(UnderlyingType.FrameworkType, length);

public override string ToString() => $"{Name}({UnderlyingType})";

public override object Read(ExtendedBinaryReader reader)
{
var length = reader.Read7BitEncodedInt();
var data = MakeArray(length);
var data = Array.CreateInstance(UnderlyingType.FrameworkType, length);
for (var i = 0; i < length; i++)
{
data.SetValue(ClearDBNull(UnderlyingType.Read(reader)), i);
Expand Down
29 changes: 29 additions & 0 deletions ClickHouse.Client/Types/JsonType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Text.Json;
using ClickHouse.Client.Formats;

namespace ClickHouse.Client.Types
{
internal class JsonType : ClickHouseType
{
public override Type FrameworkType => typeof(string);

public override object Read(ExtendedBinaryReader reader) => reader.ReadString();

public override string ToString() => "Json";

public override void Write(ExtendedBinaryWriter writer, object value)
{
if (value is null) throw new ArgumentNullException(nameof(value));
switch (value)
{
case string s:
writer.Write(s); break;
case JsonElement e:
writer.Write(e.ToString()); break;
default:
throw new NotImplementedException($"Cannot convert {value.GetType()} to Json");
}
}
}
}
28 changes: 28 additions & 0 deletions ClickHouse.Client/Types/ObjectType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using ClickHouse.Client.Formats;
using ClickHouse.Client.Types.Grammar;

namespace ClickHouse.Client.Types;

internal class ObjectType : ParameterizedType
{
public ClickHouseType UnderlyingType { get; set; }

public override Type FrameworkType => UnderlyingType.FrameworkType;

public override string Name => "Object";

public override ParameterizedType Parse(SyntaxTreeNode node, Func<SyntaxTreeNode, ClickHouseType> parseClickHouseTypeFunc, TypeSettings settings)
{
return new SimpleAggregateFunctionType
{
UnderlyingType = parseClickHouseTypeFunc(node.ChildNodes[0]),
};
}

public override object Read(ExtendedBinaryReader reader) => UnderlyingType.Read(reader);

public override string ToString() => $"{Name}({UnderlyingType})";

public override void Write(ExtendedBinaryWriter writer, object value) => UnderlyingType.Write(writer, value);
}
89 changes: 88 additions & 1 deletion ClickHouse.Client/Types/TypeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Xml.Linq;
using ClickHouse.Client.Numerics;
using ClickHouse.Client.Types.Grammar;

Expand All @@ -15,6 +16,84 @@ internal static class TypeConverter
private static readonly IDictionary<string, ParameterizedType> ParameterizedTypes = new Dictionary<string, ParameterizedType>();
private static readonly IDictionary<Type, ClickHouseType> ReverseMapping = new Dictionary<Type, ClickHouseType>();

private static readonly IDictionary<string, string> Aliases = new Dictionary<string, string>()
{
{ "BIGINT", "Int64" },
{ "BIGINT SIGNED", "Int64" },
{ "BIGINT UNSIGNED", "UInt64" },
{ "BINARY", "FixedString" },
{ "BINARY LARGE OBJECT", "String" },
{ "BINARY VARYING", "String" },
{ "BIT", "UInt64" },
{ "BLOB", "String" },
{ "BYTE", "Int8" },
{ "BYTEA", "String" },
{ "CHAR", "String" },
{ "CHAR LARGE OBJECT", "String" },
{ "CHAR VARYING", "String" },
{ "CHARACTER", "String" },
{ "CHARACTER LARGE OBJECT", "String" },
{ "CHARACTER VARYING", "String" },
{ "CLOB", "String" },
{ "DEC", "Decimal" },
{ "DOUBLE", "Float64" },
{ "DOUBLE PRECISION", "Float64" },
{ "ENUM", "Enum" },
{ "FIXED", "Decimal" },
{ "FLOAT", "Float32" },
{ "GEOMETRY", "String" },
{ "INET4", "IPv4" },
{ "INET6", "IPv6" },
{ "INT", "Int32" },
{ "INT SIGNED", "Int32" },
{ "INT UNSIGNED", "UInt32" },
{ "INT1", "Int8" },
{ "INT1 SIGNED", "Int8" },
{ "INT1 UNSIGNED", "UInt8" },
{ "INTEGER", "Int32" },
{ "INTEGER SIGNED", "Int32" },
{ "INTEGER UNSIGNED", "UInt32" },
{ "LONGBLOB", "String" },
{ "LONGTEXT", "String" },
{ "MEDIUMBLOB", "String" },
{ "MEDIUMINT", "Int32" },
{ "MEDIUMINT SIGNED", "Int32" },
{ "MEDIUMINT UNSIGNED", "UInt32" },
{ "MEDIUMTEXT", "String" },
{ "NATIONAL CHAR", "String" },
{ "NATIONAL CHAR VARYING", "String" },
{ "NATIONAL CHARACTER", "String" },
{ "NATIONAL CHARACTER LARGE OBJECT", "String" },
{ "NATIONAL CHARACTER VARYING", "String" },
{ "NCHAR", "String" },
{ "NCHAR LARGE OBJECT", "String" },
{ "NCHAR VARYING", "String" },
{ "NUMERIC", "Decimal" },
{ "NVARCHAR", "String" },
{ "REAL", "Float32" },
{ "SET", "UInt64" },
{ "SINGLE", "Float32" },
{ "SMALLINT", "Int16" },
{ "SMALLINT SIGNED", "Int16" },
{ "SMALLINT UNSIGNED", "UInt16" },
{ "TEXT", "String" },
{ "TIME", "Int64" },
{ "TIMESTAMP", "DateTime" },
{ "TINYBLOB", "String" },
{ "TINYINT", "Int8" },
{ "TINYINT SIGNED", "Int8" },
{ "TINYINT UNSIGNED", "UInt8" },
{ "TINYTEXT", "String" },
{ "VARBINARY", "String" },
{ "VARCHAR", "String" },
{ "VARCHAR2", "String" },
{ "YEAR", "UInt16" },
{ "BOOL", "Bool" },
{ "BOOLEAN", "Bool" },
{ "OBJECT('JSON')", "Json" },
{ "JSON", "Json" },
};

public static IEnumerable<string> RegisteredTypes => SimpleTypes.Keys
.Concat(ParameterizedTypes.Values.Select(t => t.Name))
.OrderBy(x => x)
Expand Down Expand Up @@ -86,6 +165,10 @@ static TypeConverter()
RegisterPlainType<PolygonType>();
RegisterPlainType<MultiPolygonType>();

// JSON/Object
RegisterPlainType<JsonType>();
RegisterParameterizedType<ObjectType>();

// Mapping fixups
ReverseMapping.Add(typeof(ClickHouseDecimal), new Decimal128Type());
ReverseMapping.Add(typeof(decimal), new Decimal128Type());
Expand Down Expand Up @@ -121,7 +204,11 @@ public static ClickHouseType ParseClickHouseType(string type, TypeSettings setti

internal static ClickHouseType ParseClickHouseType(SyntaxTreeNode node, TypeSettings settings)
{
var typeName = node.Value.Trim();
var typeName = node.Value.Trim().Trim('\'');

if (Aliases.TryGetValue(typeName.ToUpperInvariant(), out var alias))
typeName = alias;

if (typeName.Contains(' '))
{
var parts = typeName.Split(new[] { " " }, 2, StringSplitOptions.RemoveEmptyEntries);
Expand Down

0 comments on commit 91093b8

Please sign in to comment.