Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ jobs:
strategy:
fail-fast: false
matrix:
ydb-version: [ 'latest', 'trunk' ]
ydb-version: [ 'latest', '25.1' ]
services:
ydb:
image: ydbplatform/local-ydb:${{ matrix.ydb-version }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,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,64 @@
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(ISqlExpressionFactory sqlExpressionFactory) : IMethodCallTranslator
{
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[]))
{
return null;
}

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)
)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using System;
using System.Reflection;
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(
IRelationalTypeMappingSource typeMappingSource,
YdbSqlExpressionFactory sqlExpressionFactory)
: IMemberTranslator
{
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()
{
return 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,79 @@
using System;
using System.Collections.Generic;
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 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