Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for alias types #257

Merged
merged 2 commits into from
Dec 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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