Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public ClickHouseMemberTranslatorProvider(RelationalMemberTranslatorProviderDepe
: base(dependencies)
{
AddTranslators([
new ClickHouseStringTranslator(dependencies.SqlExpressionFactory),
new ClickHouseStringTranslator(dependencies.SqlExpressionFactory, typeMappingSource),
new ClickHouseArrayTranslator(dependencies.SqlExpressionFactory),
new ClickHouseMathTranslator(dependencies.SqlExpressionFactory, typeMappingSource),
new ClickHouseConvertTranslator(dependencies.SqlExpressionFactory),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public ClickHouseMethodCallTranslatorProvider(RelationalMethodCallTranslatorProv
AddTranslators([
new ClickHouseArrayTranslator(dependencies.SqlExpressionFactory),
new ClickHouseGuidTranslator(dependencies.SqlExpressionFactory),
new ClickHouseStringTranslator(dependencies.SqlExpressionFactory),
new ClickHouseStringTranslator(dependencies.SqlExpressionFactory, dependencies.RelationalTypeMappingSource),
new ClickHouseMathTranslator(dependencies.SqlExpressionFactory, dependencies.RelationalTypeMappingSource),
new ClickHouseConvertTranslator(dependencies.SqlExpressionFactory),
new ClickHouseDateTimeMethodTranslator(dependencies.SqlExpressionFactory),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
Expand Down Expand Up @@ -97,9 +99,13 @@ public class ClickHouseStringTranslator : IMethodCallTranslator, IMemberTranslat
.GetRuntimeMethod(nameof(string.Replace), [typeof(string), typeof(string)])!;

private readonly ClickHouseSqlExpressionFactory _sqlExpressionFactory;
private readonly IRelationalTypeMappingSource _relationalTypeMappingSource;

public ClickHouseStringTranslator([NotNull]ISqlExpressionFactory sqlExpressionFactory)
public ClickHouseStringTranslator(
[NotNull]ISqlExpressionFactory sqlExpressionFactory,
IRelationalTypeMappingSource relationalTypeMappingSource)
{
_relationalTypeMappingSource = relationalTypeMappingSource;
_sqlExpressionFactory = (ClickHouseSqlExpressionFactory)sqlExpressionFactory;
}

Expand Down Expand Up @@ -256,7 +262,10 @@ public ClickHouseStringTranslator([NotNull]ISqlExpressionFactory sqlExpressionFa
[argument, _sqlExpressionFactory.Constant(1), _sqlExpressionFactory.Constant(1)],
nullable: true,
argumentsPropagateNullability: [true, true, true],
method.ReturnType);
method.ReturnType,
typeMapping: (RelationalTypeMapping)_relationalTypeMappingSource
.FindMapping(typeof(string))
!.WithComposedConverter(new CharToStringConverter()));
}

if (LastOrDefaultWithoutArgs.Equals(method))
Expand All @@ -276,7 +285,8 @@ public ClickHouseStringTranslator([NotNull]ISqlExpressionFactory sqlExpressionFa
],
nullable: true,
argumentsPropagateNullability: [true, true, true],
method.ReturnType);
method.ReturnType,
typeMapping: _relationalTypeMappingSource.FindMapping(method.ReturnType));
}

if (SubstringWithStartIndex.Equals(method))
Expand All @@ -288,7 +298,8 @@ public ClickHouseStringTranslator([NotNull]ISqlExpressionFactory sqlExpressionFa
[instance!, _sqlExpressionFactory.Add(startIndex, _sqlExpressionFactory.Constant(1))],
nullable: true,
argumentsPropagateNullability: [true, true],
method.ReturnType);
method.ReturnType,
typeMapping: _relationalTypeMappingSource.FindMapping(method.ReturnType));
}

if (SubstringWithIndexAndLength.Equals(method))
Expand All @@ -301,7 +312,8 @@ public ClickHouseStringTranslator([NotNull]ISqlExpressionFactory sqlExpressionFa
[instance!, _sqlExpressionFactory.Add(startIndex, _sqlExpressionFactory.Constant(1)), length],
nullable: true,
argumentsPropagateNullability: [true, true, true],
method.ReturnType);
method.ReturnType,
typeMapping: _relationalTypeMappingSource.FindMapping(method.ReturnType));
}

if (IndexOf.Equals(method))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class ClickHouseTypeMappingSource : RelationalTypeMappingSource
private static readonly RelationalTypeMapping StringTypeMapping = new ClickHouseStringTypeMapping();
private static readonly RelationalTypeMapping BoolTypeMapping = new ClickHouseBoolTypeMapping();
private static readonly RelationalTypeMapping ByteTypeMapping = new ClickHouseByteTypeMapping();
private static readonly RelationalTypeMapping CharTypeMapping = new ClickHouseCharTypeMapping();
private static readonly RelationalTypeMapping CharTypeMapping = new ClickHouseFixedStringTypeMapping(typeof(char), true, 2);
private static readonly RelationalTypeMapping Int8TypeMapping = new ClickHouseInt8TypeMapping();
private static readonly RelationalTypeMapping Int16TypeMapping = new ClickHouseInt16TypeMapping();
private static readonly RelationalTypeMapping Int32TypeMapping = new ClickHouseInt32TypeMapping();
Expand Down Expand Up @@ -192,6 +192,16 @@ public ClickHouseTypeMappingSource(TypeMappingSourceDependencies dependencies, R
mappingInfo.Size.Value);
}

if (mappingInfo.ClrType == typeof(char))
{
var isUnicode = mappingInfo.IsUnicode ?? true;

return new ClickHouseFixedStringTypeMapping(
mappingInfo.ClrType!,
isUnicode,
isUnicode ? 2 : 1);
}

if (mappingInfo.ClrType != null && ClrTypeMappings.TryGetValue(mappingInfo.ClrType, out var map))
{
return map;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
using Microsoft.EntityFrameworkCore.Storage;
using ClickHouse.EntityFrameworkCore.Extensions;
using ClickHouse.EntityFrameworkCore.Storage.ValueConversation;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Json;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System;
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

Expand All @@ -18,21 +22,15 @@ public ClickHouseFixedStringTypeMapping(
new RelationalTypeMappingParameters(
new CoreTypeMappingParameters(
clrType: clrType,
converter: new StringToBytesConverter(
unicode ? Encoding.UTF8 : Encoding.ASCII,
new ConverterMappingHints(
size: size,
precision: null,
scale: null,
unicode: unicode)),
converter: GetConverter(clrType, unicode, size),
jsonValueReaderWriter: clrType == typeof(char)
? JsonCharReaderWriter.Instance
: clrType == typeof(string)
? JsonStringReaderWriter.Instance
: throw new ArgumentException("Argument type must be char or string", nameof(clrType))),
storeType: "FixedString",
storeTypePostfix: StoreTypePostfix.Size,
dbType: System.Data.DbType.Binary,
dbType: unicode ? System.Data.DbType.StringFixedLength : System.Data.DbType.AnsiStringFixedLength,
unicode: unicode,
size: size,
fixedLength: true,
Expand All @@ -50,8 +48,83 @@ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters p
return new ClickHouseFixedStringTypeMapping(parameters);
}

protected override void ConfigureParameter(DbParameter parameter)
{
parameter.SetStoreType(StoreType);
}

public override MethodInfo GetDataReaderMethod()
{
return typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.GetValue), [typeof(int)])!;
}

private static ValueConverter GetConverter(Type clrType, bool unicode, int size)
{
var mappingHints = new ConverterMappingHints(
size: size,
precision: null,
scale: null,
unicode: unicode);

var encoding = unicode ? Encoding.UTF8 : Encoding.ASCII;

return clrType switch
{
var t when t == typeof(char) => new ClickHouseCharToBytesConverter(encoding, mappingHints),
var t when t == typeof(char?) => new ClickHouseNullableCharToBytesConverter(encoding, mappingHints),
var t when t == typeof(string) => new ClickHouseStringToBytesConverter(encoding, mappingHints),
_ => throw new ArgumentException("Argument type must be char, char? or string", nameof(clrType))
};
}

protected override string GenerateNonNullSqlLiteral(object value)
{
if (value is byte[])
{
return "'" + Converter!.ConvertFromProvider(value) + "'";
}

return base.GenerateNonNullSqlLiteral(value);
}

// File: `src/EntityFrameworkCore.ClickHouse/Storage/Internal/Mapping/ClickHouseFixedStringTypeMapping.cs`
public override Expression CustomizeDataReaderExpression(Expression expression)
{
// expression is (object)reader.GetValue(ordinal); we need byte[] for checks
var byteArrayExpr = Expression.Convert(expression, typeof(byte[]));

var nullCheck = Expression.Equal(byteArrayExpr, Expression.Constant(null, typeof(byte[])));
var lengthProperty = Expression.Property(byteArrayExpr, nameof(Array.Length));
var lengthZeroCheck = Expression.Equal(lengthProperty, Expression.Constant(0));

// Array.TrueForAll<byte>(v, b => b == 0)
var trueForAllMethod = typeof(Array)
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Single(m =>
m.Name == nameof(Array.TrueForAll) &&
m.IsGenericMethodDefinition &&
m.GetGenericArguments().Length == 1 &&
m.GetParameters().Length == 2)
.MakeGenericMethod(typeof(byte));

var b = Expression.Parameter(typeof(byte), "b");
var predicate = Expression.Lambda<Predicate<byte>>(
Expression.Equal(b, Expression.Constant((byte)0)),
b);

var allZeroCheck = Expression.Call(trueForAllMethod, byteArrayExpr, predicate);

// return null only for (null || empty || all-zero)
var condition = Expression.OrElse(Expression.OrElse(nullCheck, lengthZeroCheck), allZeroCheck);

// keep type as object for EF shaper, but return the byte[] value in the "else" branch
var result = Expression.Condition(
condition,
Expression.Constant(null, typeof(object)),
Expression.Convert(byteArrayExpr, typeof(object)));

return result;
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace ClickHouse.EntityFrameworkCore.Storage.ValueConversation;

public sealed class ClickHouseCharToBytesConverter : ValueConverter<char, byte[]>
{
private static readonly MethodInfo EncodingGetBytesMethodInfo =
typeof(Encoding).GetMethod(nameof(Encoding.GetBytes), [typeof(string)])!;

private static readonly MethodInfo EncodingGetStringMethodInfo =
typeof(Encoding).GetMethod(nameof(Encoding.GetString), [typeof(byte[])])!;

private static readonly MethodInfo EncodingGetEncodingMethodInfo =
typeof(Encoding).GetMethod(nameof(Encoding.GetEncoding), [typeof(int)])!;

private static readonly MethodInfo StringGetCharsMethodInfo =
typeof(string).GetMethod("get_Chars", [typeof(int)])!;

public ClickHouseCharToBytesConverter(Encoding encoding, ConverterMappingHints mappingHints)
: base(ToProvider(encoding), FromProvider(encoding), mappingHints)
{
}

private static Expression<Func<char, byte[]>> ToProvider(Encoding encoding)
{
// c => Encoding.GetEncoding(cp).GetBytes(c.ToString())
var prm = Expression.Parameter(typeof(char), "c");
var toStringMethod = typeof(char).GetMethod(nameof(char.ToString), Type.EmptyTypes)!;

var getEncodingCall = Expression.Call(
EncodingGetEncodingMethodInfo,
Expression.Constant(encoding.CodePage));

var getBytesCall = Expression.Call(
getEncodingCall,
EncodingGetBytesMethodInfo,
Expression.Call(prm, toStringMethod));

return Expression.Lambda<Func<char, byte[]>>(getBytesCall, prm);
}

private static Expression<Func<byte[], char>> FromProvider(Encoding encoding)
{
// v => Encoding.GetEncoding(cp).GetString(v)[0]
var prm = Expression.Parameter(typeof(byte[]), "v");

var getStringCall = Expression.Call(
Expression.Call(EncodingGetEncodingMethodInfo, Expression.Constant(encoding.CodePage)),
EncodingGetStringMethodInfo,
prm);

var getChar = Expression.Call(getStringCall, StringGetCharsMethodInfo, Expression.Constant(0));

return Expression.Lambda<Func<byte[], char>>(getChar, prm);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;

namespace ClickHouse.EntityFrameworkCore.Storage.ValueConversation;

public class ClickHouseCharToStringConverter : ValueConverter<char?, string?>
{
public ClickHouseCharToStringConverter(ConverterMappingHints? mappingHints = null)
: base(
c => c == null ? null : c.ToString(),
s => string.IsNullOrEmpty(s) ? null : s[0],
mappingHints)
{
}
}

This file was deleted.

Loading
Loading