Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -16,6 +16,7 @@
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Update;
using Microsoft.EntityFrameworkCore.ValueGeneration;
using Microsoft.Extensions.DependencyInjection;

namespace EntityFrameworkCore.Ydb.Extensions;
Expand All @@ -42,10 +43,14 @@ public static IServiceCollection AddEntityFrameworkYdb(this IServiceCollection s
.TryAdd<IHistoryRepository, YdbHistoryRepository>()
.TryAdd<IQueryableMethodTranslatingExpressionVisitorFactory,
YdbQueryableMethodTranslatingExpressionVisitorFactory>()
.TryAdd<IExecutionStrategyFactory, YdbExecutionStrategyFactory>()
.TryAdd<IMethodCallTranslatorProvider, YdbMethodCallTranslatorProvider>()
.TryAdd<IAggregateMethodCallTranslatorProvider, YdbAggregateMethodCallTranslatorProvider>()
.TryAdd<IMemberTranslatorProvider, YdbMemberTranslatorProvider>()
.TryAdd<IQuerySqlGeneratorFactory, YdbQuerySqlGeneratorFactory>()
#pragma warning disable EF9002
.TryAdd<ISqlAliasManagerFactory, YdbSqlAliasManagerFactory>()
#pragma warning restore EF9002
.TryAdd<IRelationalSqlTranslatingExpressionVisitorFactory, YdbSqlTranslatingExpressionVisitorFactory>()
.TryAdd<IQueryTranslationPostprocessorFactory, YdbQueryTranslationPostprocessorFactory>()
.TryAdd<IRelationalParameterBasedSqlProcessorFactory, YdbParameterBasedSqlProcessorFactory>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using EntityFrameworkCore.Ydb.Utilities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;

namespace EntityFrameworkCore.Ydb.Query.Internal.Translators;

public class YdbByteArrayMethodTranslator : IMethodCallTranslator
{
private readonly ISqlExpressionFactory _sqlExpressionFactory;

public YdbByteArrayMethodTranslator(ISqlExpressionFactory sqlExpressionFactory)
=> _sqlExpressionFactory = sqlExpressionFactory;

private static MethodInfo Contains => typeof(Enumerable)
.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
.Where(m => m.Name == nameof(Enumerable.Contains))
.Single(mi => mi.IsGenericMethod &&
mi.GetGenericArguments().Length == 1 &&
mi.GetParameters()
.Select(e => e.ParameterType)
.SequenceEqual(
[
typeof(IEnumerable<>).MakeGenericType(mi.GetGenericArguments()[0]),
mi.GetGenericArguments()[0]
]
)
);

public virtual SqlExpression? Translate(
SqlExpression? instance,
MethodInfo method,
IReadOnlyList<SqlExpression> arguments,
IDiagnosticsLogger<DbLoggerCategory.Query> logger
)
{
if (method.IsGenericMethod
&& method.GetGenericMethodDefinition().Equals(Contains)
&& arguments[0].Type == typeof(byte[]))
{
var source = arguments[0];

var value = arguments[1] is SqlConstantExpression constantValue
? _sqlExpressionFactory.Constant(new[] { (byte)constantValue.Value! }, source.TypeMapping)
: _sqlExpressionFactory.Function(
"ToBytes",
[arguments[1]],
nullable: false,
argumentsPropagateNullability: ArrayUtil.TrueArrays[1],
typeof(string));

return _sqlExpressionFactory.IsNotNull(
_sqlExpressionFactory.Function(
"FIND",
[source, value],
nullable: true,
argumentsPropagateNullability: ArrayUtil.FalseArrays[2],
typeof(int)
)
);
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using EntityFrameworkCore.Ydb.Storage.Internal;
using EntityFrameworkCore.Ydb.Storage.Internal.Mapping;
using EntityFrameworkCore.Ydb.Utilities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;

namespace EntityFrameworkCore.Ydb.Query.Internal.Translators;

public class YdbDateTimeMemberTranslator : IMemberTranslator
{
private readonly IRelationalTypeMappingSource _typeMappingSource;
private readonly YdbSqlExpressionFactory _sqlExpressionFactory;
private readonly RelationalTypeMapping _timestampMapping;

public YdbDateTimeMemberTranslator(
IRelationalTypeMappingSource typeMappingSource,
YdbSqlExpressionFactory sqlExpressionFactory
)
{
_typeMappingSource = typeMappingSource;
_timestampMapping = typeMappingSource.FindMapping(typeof(DateTime), "TimeStamp")!;
_sqlExpressionFactory = sqlExpressionFactory;
}

public virtual SqlExpression? Translate(
SqlExpression? instance,
MemberInfo member,
Type returnType,
IDiagnosticsLogger<DbLoggerCategory.Query> logger
)
{
var declaringType = member.DeclaringType;

if (declaringType == typeof(TimeOnly))
{
throw new InvalidOperationException("Ydb doesn't support TimeOnly right now");
}

if (declaringType != typeof(DateTime) && declaringType != typeof(DateOnly))
{
return null;
}

if (member.Name == nameof(DateTime.Date))
{
switch (instance)
{
case { TypeMapping: YdbDateTimeTypeMapping }:
case { Type: var type } when type == typeof(DateTime):
return _sqlExpressionFactory.Convert(
_sqlExpressionFactory.Convert(instance, typeof(DateOnly)),
typeof(DateTime)
);
case { TypeMapping: YdbDateOnlyTypeMapping }:
case { Type: var type } when type == typeof(DateOnly):
return instance;
default:
return null;
}
}

return member.Name switch
{
// TODO: Find out how to add
// nameof(DateTime.Now) => ???,
// nameof(DateTime.Today) => ???

nameof(DateTime.UtcNow) => UtcNow(),

nameof(DateTime.Year) => DatePart(instance!, "GetYear"),
nameof(DateTime.Month) => DatePart(instance!, "GetMonth"),
nameof(DateTime.Day) => DatePart(instance!, "GetDayOfMonth"),
nameof(DateTime.Hour) => DatePart(instance!, "GetHour"),
nameof(DateTime.Minute) => DatePart(instance!, "GetMinute"),
nameof(DateTime.Second) => DatePart(instance!, "GetSecond"),
nameof(DateTime.Millisecond) => DatePart(instance!, "GetMillisecondOfSecond"),

nameof(DateTime.DayOfYear) => DatePart(instance!, "GetDayOfYear"),
nameof(DateTime.DayOfWeek) => DatePart(instance!, "GetDayOfWeek"),

// TODO: Research if it's possible to implement
nameof(DateTime.Ticks) => null,
_ => null
};

SqlExpression UtcNow()
=> _sqlExpressionFactory.Function(
"CurrentUtc" + returnType.Name == "DateOnly" ? "Date" : returnType.Name,
[],
nullable: false,
argumentsPropagateNullability: ArrayUtil.TrueArrays[0],
returnType,
_typeMappingSource.FindMapping(returnType)
);
}

private SqlExpression? DatePart(SqlExpression instance, string partName)
{
var result = _sqlExpressionFactory.Function(
$"DateTime::{partName}",
[instance],
nullable: true,
argumentsPropagateNullability: ArrayUtil.TrueArrays[1],
typeof(short) // Doesn't matter because we cast it to int in next line anyway
);

return _sqlExpressionFactory.Convert(result, typeof(int));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using EntityFrameworkCore.Ydb.Utilities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;

namespace EntityFrameworkCore.Ydb.Query.Internal.Translators;

public class YdbDateTimeMethodTranslator : IMethodCallTranslator
{
private static readonly Dictionary<MethodInfo, string> MethodInfoDatePartMapping = new()
{
{ typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.AddYears), [typeof(int)])!, " Years" },
{ typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.AddMonths), [typeof(int)])!, " Months" },
{ typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.AddDays), [typeof(int)])!, " Days" },

{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddYears), [typeof(int)])!, "Years" },
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddMonths), [typeof(int)])!, "Months" },
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddDays), [typeof(double)])!, "Days" },
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddHours), [typeof(double)])!, "Hours" },
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddMinutes), [typeof(double)])!, "Mins" },
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddSeconds), [typeof(double)])!, "Secs" },

{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddYears), [typeof(int)])!, "Years" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddMonths), [typeof(int)])!, "Months" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddDays), [typeof(double)])!, "Days" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddHours), [typeof(double)])!, "Hours" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddMinutes), [typeof(double)])!, "Mins" },
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddSeconds), [typeof(double)])!, "Secs" },
};

private readonly YdbSqlExpressionFactory _sqlExpressionFactory;

public YdbDateTimeMethodTranslator(YdbSqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}


public virtual SqlExpression? Translate(
SqlExpression? instance,
MethodInfo method,
IReadOnlyList<SqlExpression> arguments,
IDiagnosticsLogger<DbLoggerCategory.Query> logger
) => TranslateDatePart(instance, method, arguments);

private SqlExpression? TranslateDatePart(
SqlExpression? instance,
MethodInfo method,
IReadOnlyList<SqlExpression> arguments
)
{
if (
instance is not null
&& MethodInfoDatePartMapping.TryGetValue(method, out var datePart))
{
var shiftDatePartFunction = _sqlExpressionFactory.Function(
"DateTime::Shift" + datePart,
[instance, arguments[0]],
nullable: true,
ArrayUtil.TrueArrays[2],
returnType: typeof(DateTime)
);

return _sqlExpressionFactory.Function(
"DateTime::MakeDate",
arguments: [shiftDatePartFunction],
nullable: true,
ArrayUtil.TrueArrays[1],
returnType: typeof(DateTime)
);
}

return null;
}
}
Loading
Loading