Skip to content

Commit 75e7112

Browse files
Functional tests with some fixes [2/?] (#307)
Co-authored-by: KirillKurdyukov <[email protected]>
1 parent 866b0d5 commit 75e7112

File tree

106 files changed

+4679
-232
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+4679
-232
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ jobs:
100100
strategy:
101101
fail-fast: false
102102
matrix:
103-
ydb-version: [ 'latest', 'trunk' ]
103+
ydb-version: [ 'latest', '25.1' ]
104104
services:
105105
ydb:
106106
image: ydbplatform/local-ydb:${{ matrix.ydb-version }}

src/EFCore.Ydb/src/Extensions/YdbServiceCollectionExtensions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,14 @@ public static IServiceCollection AddEntityFrameworkYdb(this IServiceCollection s
4242
.TryAdd<IHistoryRepository, YdbHistoryRepository>()
4343
.TryAdd<IQueryableMethodTranslatingExpressionVisitorFactory,
4444
YdbQueryableMethodTranslatingExpressionVisitorFactory>()
45+
.TryAdd<IExecutionStrategyFactory, YdbExecutionStrategyFactory>()
4546
.TryAdd<IMethodCallTranslatorProvider, YdbMethodCallTranslatorProvider>()
4647
.TryAdd<IAggregateMethodCallTranslatorProvider, YdbAggregateMethodCallTranslatorProvider>()
4748
.TryAdd<IMemberTranslatorProvider, YdbMemberTranslatorProvider>()
4849
.TryAdd<IQuerySqlGeneratorFactory, YdbQuerySqlGeneratorFactory>()
50+
#pragma warning disable EF9002
51+
.TryAdd<ISqlAliasManagerFactory, YdbSqlAliasManagerFactory>()
52+
#pragma warning restore EF9002
4953
.TryAdd<IRelationalSqlTranslatingExpressionVisitorFactory, YdbSqlTranslatingExpressionVisitorFactory>()
5054
.TryAdd<IQueryTranslationPostprocessorFactory, YdbQueryTranslationPostprocessorFactory>()
5155
.TryAdd<IRelationalParameterBasedSqlProcessorFactory, YdbParameterBasedSqlProcessorFactory>()
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Reflection;
4+
using EntityFrameworkCore.Ydb.Utilities;
5+
using Microsoft.EntityFrameworkCore;
6+
using Microsoft.EntityFrameworkCore.Diagnostics;
7+
using Microsoft.EntityFrameworkCore.Query;
8+
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
9+
10+
namespace EntityFrameworkCore.Ydb.Query.Internal.Translators;
11+
12+
public class YdbByteArrayMethodTranslator(ISqlExpressionFactory sqlExpressionFactory) : IMethodCallTranslator
13+
{
14+
private static MethodInfo Contains => typeof(Enumerable)
15+
.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
16+
.Where(m => m.Name == nameof(Enumerable.Contains))
17+
.Single(mi => mi.IsGenericMethod &&
18+
mi.GetGenericArguments().Length == 1 &&
19+
mi.GetParameters()
20+
.Select(e => e.ParameterType)
21+
.SequenceEqual(
22+
[
23+
typeof(IEnumerable<>).MakeGenericType(mi.GetGenericArguments()[0]),
24+
mi.GetGenericArguments()[0]
25+
]
26+
)
27+
);
28+
29+
public virtual SqlExpression? Translate(
30+
SqlExpression? instance,
31+
MethodInfo method,
32+
IReadOnlyList<SqlExpression> arguments,
33+
IDiagnosticsLogger<DbLoggerCategory.Query> logger
34+
)
35+
{
36+
if (!method.IsGenericMethod
37+
|| !method.GetGenericMethodDefinition().Equals(Contains)
38+
|| arguments[0].Type != typeof(byte[]))
39+
{
40+
return null;
41+
}
42+
43+
var source = arguments[0];
44+
45+
var value = arguments[1] is SqlConstantExpression constantValue
46+
? sqlExpressionFactory.Constant(new[] { (byte)constantValue.Value! }, source.TypeMapping)
47+
: sqlExpressionFactory.Function(
48+
"ToBytes",
49+
[arguments[1]],
50+
nullable: false,
51+
argumentsPropagateNullability: ArrayUtil.TrueArrays[1],
52+
typeof(string));
53+
54+
return sqlExpressionFactory.IsNotNull(
55+
sqlExpressionFactory.Function(
56+
"FIND",
57+
[source, value],
58+
nullable: true,
59+
argumentsPropagateNullability: ArrayUtil.FalseArrays[2],
60+
typeof(int)
61+
)
62+
);
63+
}
64+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using System;
2+
using System.Reflection;
3+
using EntityFrameworkCore.Ydb.Storage.Internal.Mapping;
4+
using EntityFrameworkCore.Ydb.Utilities;
5+
using Microsoft.EntityFrameworkCore;
6+
using Microsoft.EntityFrameworkCore.Diagnostics;
7+
using Microsoft.EntityFrameworkCore.Query;
8+
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
9+
using Microsoft.EntityFrameworkCore.Storage;
10+
11+
namespace EntityFrameworkCore.Ydb.Query.Internal.Translators;
12+
13+
public class YdbDateTimeMemberTranslator(
14+
IRelationalTypeMappingSource typeMappingSource,
15+
YdbSqlExpressionFactory sqlExpressionFactory)
16+
: IMemberTranslator
17+
{
18+
public virtual SqlExpression? Translate(
19+
SqlExpression? instance,
20+
MemberInfo member,
21+
Type returnType,
22+
IDiagnosticsLogger<DbLoggerCategory.Query> logger
23+
)
24+
{
25+
var declaringType = member.DeclaringType;
26+
27+
if (declaringType == typeof(TimeOnly))
28+
{
29+
throw new InvalidOperationException("Ydb doesn't support TimeOnly right now");
30+
}
31+
32+
if (declaringType != typeof(DateTime) && declaringType != typeof(DateOnly))
33+
{
34+
return null;
35+
}
36+
37+
if (member.Name == nameof(DateTime.Date))
38+
{
39+
switch (instance)
40+
{
41+
case { TypeMapping: YdbDateTimeTypeMapping }:
42+
case { Type: var type } when type == typeof(DateTime):
43+
return sqlExpressionFactory.Convert(
44+
sqlExpressionFactory.Convert(instance, typeof(DateOnly)),
45+
typeof(DateTime)
46+
);
47+
case { TypeMapping: YdbDateOnlyTypeMapping }:
48+
case { Type: var type } when type == typeof(DateOnly):
49+
return instance;
50+
default:
51+
return null;
52+
}
53+
}
54+
55+
return member.Name switch
56+
{
57+
// TODO: Find out how to add
58+
// nameof(DateTime.Now) => ???,
59+
// nameof(DateTime.Today) => ???
60+
61+
nameof(DateTime.UtcNow) => UtcNow(),
62+
63+
nameof(DateTime.Year) => DatePart(instance!, "GetYear"),
64+
nameof(DateTime.Month) => DatePart(instance!, "GetMonth"),
65+
nameof(DateTime.Day) => DatePart(instance!, "GetDayOfMonth"),
66+
nameof(DateTime.Hour) => DatePart(instance!, "GetHour"),
67+
nameof(DateTime.Minute) => DatePart(instance!, "GetMinute"),
68+
nameof(DateTime.Second) => DatePart(instance!, "GetSecond"),
69+
nameof(DateTime.Millisecond) => DatePart(instance!, "GetMillisecondOfSecond"),
70+
71+
nameof(DateTime.DayOfYear) => DatePart(instance!, "GetDayOfYear"),
72+
nameof(DateTime.DayOfWeek) => DatePart(instance!, "GetDayOfWeek"),
73+
74+
// TODO: Research if it's possible to implement
75+
nameof(DateTime.Ticks) => null,
76+
_ => null
77+
};
78+
79+
SqlExpression UtcNow()
80+
{
81+
return sqlExpressionFactory.Function(
82+
"CurrentUtc" + returnType.Name == "DateOnly" ? "Date" : returnType.Name,
83+
[],
84+
nullable: false,
85+
argumentsPropagateNullability: ArrayUtil.TrueArrays[0],
86+
returnType,
87+
typeMappingSource.FindMapping(returnType)
88+
);
89+
}
90+
}
91+
92+
private SqlExpression DatePart(SqlExpression instance, string partName)
93+
{
94+
var result = sqlExpressionFactory.Function(
95+
$"DateTime::{partName}",
96+
[instance],
97+
nullable: true,
98+
argumentsPropagateNullability: ArrayUtil.TrueArrays[1],
99+
typeof(short) // Doesn't matter because we cast it to int in next line anyway
100+
);
101+
102+
return sqlExpressionFactory.Convert(result, typeof(int));
103+
}
104+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Reflection;
4+
using EntityFrameworkCore.Ydb.Utilities;
5+
using Microsoft.EntityFrameworkCore;
6+
using Microsoft.EntityFrameworkCore.Diagnostics;
7+
using Microsoft.EntityFrameworkCore.Query;
8+
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
9+
10+
namespace EntityFrameworkCore.Ydb.Query.Internal.Translators;
11+
12+
public class YdbDateTimeMethodTranslator : IMethodCallTranslator
13+
{
14+
private static readonly Dictionary<MethodInfo, string> MethodInfoDatePartMapping = new()
15+
{
16+
{ typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.AddYears), [typeof(int)])!, " Years" },
17+
{ typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.AddMonths), [typeof(int)])!, " Months" },
18+
{ typeof(DateOnly).GetRuntimeMethod(nameof(DateOnly.AddDays), [typeof(int)])!, " Days" },
19+
20+
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddYears), [typeof(int)])!, "Years" },
21+
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddMonths), [typeof(int)])!, "Months" },
22+
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddDays), [typeof(double)])!, "Days" },
23+
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddHours), [typeof(double)])!, "Hours" },
24+
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddMinutes), [typeof(double)])!, "Mins" },
25+
{ typeof(DateTime).GetRuntimeMethod(nameof(DateTime.AddSeconds), [typeof(double)])!, "Secs" },
26+
27+
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddYears), [typeof(int)])!, "Years" },
28+
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddMonths), [typeof(int)])!, "Months" },
29+
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddDays), [typeof(double)])!, "Days" },
30+
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddHours), [typeof(double)])!, "Hours" },
31+
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddMinutes), [typeof(double)])!, "Mins" },
32+
{ typeof(DateTimeOffset).GetRuntimeMethod(nameof(DateTimeOffset.AddSeconds), [typeof(double)])!, "Secs" }
33+
};
34+
35+
private readonly YdbSqlExpressionFactory _sqlExpressionFactory;
36+
37+
public YdbDateTimeMethodTranslator(YdbSqlExpressionFactory sqlExpressionFactory)
38+
{
39+
_sqlExpressionFactory = sqlExpressionFactory;
40+
}
41+
42+
43+
public virtual SqlExpression? Translate(
44+
SqlExpression? instance,
45+
MethodInfo method,
46+
IReadOnlyList<SqlExpression> arguments,
47+
IDiagnosticsLogger<DbLoggerCategory.Query> logger
48+
) => TranslateDatePart(instance, method, arguments);
49+
50+
private SqlExpression? TranslateDatePart(
51+
SqlExpression? instance,
52+
MethodInfo method,
53+
IReadOnlyList<SqlExpression> arguments
54+
)
55+
{
56+
if (
57+
instance is not null
58+
&& MethodInfoDatePartMapping.TryGetValue(method, out var datePart))
59+
{
60+
var shiftDatePartFunction = _sqlExpressionFactory.Function(
61+
"DateTime::Shift" + datePart,
62+
[instance, arguments[0]],
63+
nullable: true,
64+
ArrayUtil.TrueArrays[2],
65+
returnType: typeof(DateTime)
66+
);
67+
68+
return _sqlExpressionFactory.Function(
69+
"DateTime::MakeDate",
70+
arguments: [shiftDatePartFunction],
71+
nullable: true,
72+
ArrayUtil.TrueArrays[1],
73+
returnType: typeof(DateTime)
74+
);
75+
}
76+
77+
return null;
78+
}
79+
}

0 commit comments

Comments
 (0)