Skip to content

Commit 1babd7a

Browse files
committed
Functional tests with some fixes [2/?]
1 parent df579f2 commit 1babd7a

File tree

78 files changed

+1729
-188
lines changed

Some content is hidden

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

78 files changed

+1729
-188
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using Microsoft.EntityFrameworkCore.Query;
1717
using Microsoft.EntityFrameworkCore.Storage;
1818
using Microsoft.EntityFrameworkCore.Update;
19+
using Microsoft.EntityFrameworkCore.ValueGeneration;
1920
using Microsoft.Extensions.DependencyInjection;
2021

2122
namespace EntityFrameworkCore.Ydb.Extensions;
@@ -42,10 +43,14 @@ public static IServiceCollection AddEntityFrameworkYdb(this IServiceCollection s
4243
.TryAdd<IHistoryRepository, YdbHistoryRepository>()
4344
.TryAdd<IQueryableMethodTranslatingExpressionVisitorFactory,
4445
YdbQueryableMethodTranslatingExpressionVisitorFactory>()
46+
.TryAdd<IExecutionStrategyFactory, YdbExecutionStrategyFactory>()
4547
.TryAdd<IMethodCallTranslatorProvider, YdbMethodCallTranslatorProvider>()
4648
.TryAdd<IAggregateMethodCallTranslatorProvider, YdbAggregateMethodCallTranslatorProvider>()
4749
.TryAdd<IMemberTranslatorProvider, YdbMemberTranslatorProvider>()
4850
.TryAdd<IQuerySqlGeneratorFactory, YdbQuerySqlGeneratorFactory>()
51+
#pragma warning disable EF9002
52+
.TryAdd<ISqlAliasManagerFactory, YdbSqlAliasManagerFactory>()
53+
#pragma warning restore EF9002
4954
.TryAdd<IRelationalSqlTranslatingExpressionVisitorFactory, YdbSqlTranslatingExpressionVisitorFactory>()
5055
.TryAdd<IQueryTranslationPostprocessorFactory, YdbQueryTranslationPostprocessorFactory>()
5156
.TryAdd<IRelationalParameterBasedSqlProcessorFactory, YdbParameterBasedSqlProcessorFactory>()

src/EFCore.Ydb/src/Query/Internal/YdbQuerySqlGenerator.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,4 +264,60 @@ protected override Expression VisitJsonScalar(JsonScalarExpression jsonScalarExp
264264
Sql.Append("\")");
265265
return jsonScalarExpression;
266266
}
267+
268+
protected override Expression VisitProjection(ProjectionExpression projectionExpression)
269+
{
270+
Visit(projectionExpression.Expression);
271+
272+
if (projectionExpression.Alias != string.Empty)
273+
{
274+
Sql
275+
.Append(AliasSeparator)
276+
.Append(SqlGenerationHelper.DelimitIdentifier(projectionExpression.Alias));
277+
}
278+
279+
return projectionExpression;
280+
}
281+
282+
protected override Expression VisitCase(CaseExpression caseExpression)
283+
{
284+
Sql.Append("CASE");
285+
286+
if (caseExpression.Operand != null)
287+
{
288+
Sql.Append(" ");
289+
Visit(caseExpression.Operand);
290+
}
291+
292+
using (Sql.Indent())
293+
{
294+
foreach (var whenClause in caseExpression.WhenClauses)
295+
{
296+
Sql
297+
.AppendLine()
298+
.Append("WHEN ");
299+
Visit(whenClause.Test);
300+
Sql.Append(" THEN ");
301+
Visit(whenClause.Result);
302+
}
303+
304+
Sql
305+
.AppendLine()
306+
.Append("ELSE ");
307+
if (caseExpression.ElseResult != null)
308+
{
309+
Visit(caseExpression.ElseResult);
310+
}
311+
else
312+
{
313+
Sql.Append("NULL");
314+
}
315+
}
316+
317+
Sql
318+
.AppendLine()
319+
.Append("END");
320+
321+
return caseExpression;
322+
}
267323
}

src/EFCore.Ydb/src/Query/Internal/YdbQueryableMethodTranslatingExpressionVisitor.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Linq.Expressions;
12
using EntityFrameworkCore.Ydb.Storage.Internal;
23
using Microsoft.EntityFrameworkCore.Query;
34

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
using System.Collections.Generic;
2+
using System.Linq.Expressions;
3+
using Microsoft.EntityFrameworkCore.Query;
4+
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
5+
6+
namespace EntityFrameworkCore.Ydb.Query.Internal;
7+
8+
public class YdbSqlAliasManager : SqlAliasManager
9+
{
10+
public override Expression PostprocessAliases(Expression expression)
11+
{
12+
var aliasRewriter = new AliasRewriter();
13+
return base.PostprocessAliases(aliasRewriter.Visit(expression));
14+
}
15+
16+
// TODO: Temporary solution to solve following problem (https://t.me/ydb_ru/28649)
17+
// Doesn't work in all cases. Should be improved
18+
private sealed class AliasRewriter : ExpressionVisitor
19+
{
20+
protected override Expression VisitExtension(Expression node) => node switch
21+
{
22+
ShapedQueryExpression shapedQuery => shapedQuery.UpdateQueryExpression(Visit(shapedQuery.QueryExpression)),
23+
SelectExpression selectExpression => VisitSelect(selectExpression),
24+
_ => base.VisitExtension(node)
25+
};
26+
27+
private Expression VisitSelect(SelectExpression selectExpression)
28+
{
29+
var newTables = new List<TableExpressionBase>(selectExpression.Tables.Count);
30+
foreach (var table in selectExpression.Tables)
31+
{
32+
// We cannot change type in current expressionVisitor. Only adjust aliases
33+
newTables.Add((Visit(table) as TableExpressionBase)!);
34+
}
35+
36+
var newProjections = AdjustAliases(selectExpression.Projection);
37+
38+
return selectExpression.Update(
39+
tables: newTables,
40+
predicate: selectExpression.Predicate,
41+
groupBy: selectExpression.GroupBy,
42+
having: selectExpression.Having,
43+
projections: newProjections,
44+
orderings: selectExpression.Orderings,
45+
offset: selectExpression.Offset,
46+
limit: selectExpression.Limit
47+
);
48+
}
49+
50+
private Expression VisitTableBase(TableExpressionBase tableExpression)
51+
=> tableExpression switch
52+
{
53+
TableExpression t => VisitTable(t),
54+
SelectExpression selectExpression => VisitSelect(selectExpression),
55+
LeftJoinExpression leftJoinExpression => VisitLeftJoin(leftJoinExpression),
56+
_ => Visit(tableExpression)
57+
};
58+
59+
private Expression VisitLeftJoin(LeftJoinExpression leftJoinExpression)
60+
=> leftJoinExpression.Update(
61+
(VisitTableBase(leftJoinExpression.Table) as TableExpressionBase)!,
62+
leftJoinExpression.JoinPredicate
63+
);
64+
65+
private Expression VisitTable(TableExpressionBase tableExpression)
66+
{
67+
return tableExpression;
68+
}
69+
70+
private IReadOnlyList<ProjectionExpression> AdjustAliases(IReadOnlyList<ProjectionExpression> projections)
71+
{
72+
var newProjections = new ProjectionExpression[projections.Count];
73+
var knownAliases = new Dictionary<string, int>();
74+
var isTrueAlias = new bool[projections.Count];
75+
76+
for (var i = 0; i < projections.Count; i++)
77+
{
78+
if (projections[i].Alias != string.Empty)
79+
{
80+
if (projections[i].Expression is ColumnExpression columnExpression &&
81+
columnExpression.Name == projections[i].Alias)
82+
{
83+
isTrueAlias[i] = false;
84+
knownAliases.TryAdd(projections[i].Alias, i);
85+
}
86+
else
87+
{
88+
isTrueAlias[i] = true;
89+
knownAliases[projections[i].Alias] = i;
90+
}
91+
}
92+
else
93+
{
94+
isTrueAlias[i] = false;
95+
}
96+
}
97+
98+
for (var i = 0; i < projections.Count; i++)
99+
{
100+
if (isTrueAlias[i])
101+
{
102+
newProjections[i] = projections[i];
103+
continue;
104+
}
105+
106+
var currentProjection = projections[i];
107+
int? key = knownAliases.TryGetValue(currentProjection.Alias, out var aliasPosition)
108+
? aliasPosition
109+
: null;
110+
111+
if (key == i)
112+
{
113+
newProjections[i] = currentProjection;
114+
}
115+
else
116+
{
117+
newProjections[i] =
118+
new ProjectionExpression(currentProjection.Expression, alias: string.Empty);
119+
}
120+
}
121+
122+
return newProjections;
123+
}
124+
}
125+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
using Microsoft.EntityFrameworkCore.Query;
3+
4+
namespace EntityFrameworkCore.Ydb.Query.Internal;
5+
6+
[Experimental("EF9002")]
7+
public class YdbSqlAliasManagerFactory : ISqlAliasManagerFactory
8+
{
9+
public SqlAliasManager Create()
10+
=> new YdbSqlAliasManager();
11+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System;
2+
using System.Data;
3+
using Microsoft.EntityFrameworkCore.Storage;
4+
5+
namespace EntityFrameworkCore.Ydb.Storage.Internal.Mapping;
6+
7+
public class YdbDateTimeTypeMapping : DateTimeTypeMapping
8+
{
9+
private const string DateTimeFormatConst = @"{0:yyyy-MM-dd HH\:mm\:ss.fffffff}";
10+
11+
private string StoreTypeLiteral { get; }
12+
13+
public YdbDateTimeTypeMapping(
14+
string storeType,
15+
DbType? dbType,
16+
Type clrType
17+
) : base(storeType, dbType)
18+
{
19+
StoreTypeLiteral = storeType;
20+
}
21+
22+
protected YdbDateTimeTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters)
23+
{
24+
}
25+
26+
protected override string SqlLiteralFormatString
27+
=> "CAST('" + DateTimeFormatConst + $"' AS {StoreTypeLiteral})";
28+
}

src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbDecimalTypeMapping.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,5 @@ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters p
2727

2828
protected override string ProcessStoreType(
2929
RelationalTypeMappingParameters parameters, string storeType, string storeTypeNameBase
30-
) => $"{storeType}({parameters.Precision ?? DefaultPrecision}, {parameters.Scale ?? DefaultScale})";
30+
) => $"Decimal({parameters.Precision ?? DefaultPrecision}, {parameters.Scale ?? DefaultScale})";
3131
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using Microsoft.EntityFrameworkCore.Storage;
2+
using Microsoft.EntityFrameworkCore.Storage.Json;
3+
4+
namespace EntityFrameworkCore.Ydb.Storage.Internal.Mapping;
5+
6+
public class YdbTextTypeMapping : RelationalTypeMapping
7+
{
8+
public static YdbTextTypeMapping Default { get; } = new("TEXT");
9+
10+
public YdbTextTypeMapping(string storeType)
11+
: base(
12+
new RelationalTypeMappingParameters(
13+
new CoreTypeMappingParameters(
14+
typeof(string),
15+
jsonValueReaderWriter: JsonStringReaderWriter.Instance
16+
),
17+
storeType: storeType,
18+
storeTypePostfix: StoreTypePostfix.None,
19+
dbType: System.Data.DbType.String,
20+
unicode: true
21+
)
22+
)
23+
{
24+
}
25+
26+
protected YdbTextTypeMapping(RelationalTypeMappingParameters parameters)
27+
: base(parameters)
28+
{
29+
}
30+
31+
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
32+
=> new YdbTextTypeMapping(parameters);
33+
34+
protected virtual string EscapeSqlLiteral(string literal) => literal.Replace("'", "\\'");
35+
36+
protected override string GenerateNonNullSqlLiteral(object value) => $"'{EscapeSqlLiteral((string)value)}'u";
37+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System;
2+
using Microsoft.EntityFrameworkCore.Storage;
3+
using Ydb.Sdk.Ado;
4+
5+
namespace EntityFrameworkCore.Ydb.Storage.Internal;
6+
7+
public class YdbExecutionStrategy(ExecutionStrategyDependencies dependencies)
8+
: ExecutionStrategy(dependencies, maxRetryCount: 10, maxRetryDelay: TimeSpan.FromSeconds(10))
9+
{
10+
protected override bool ShouldRetryOn(Exception exception)
11+
=> exception is YdbException ydbException && (
12+
TransactionLockInvalidated(ydbException)
13+
|| false // For other possible exceptions
14+
);
15+
16+
private static bool TransactionLockInvalidated(YdbException exception)
17+
=> exception.Message.Contains("Transaction locks invalidated");
18+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Microsoft.EntityFrameworkCore.Storage;
2+
3+
namespace EntityFrameworkCore.Ydb.Storage.Internal;
4+
5+
public class YdbExecutionStrategyFactory(ExecutionStrategyDependencies dependencies)
6+
: RelationalExecutionStrategyFactory(dependencies)
7+
{
8+
protected override IExecutionStrategy CreateDefaultStrategy(ExecutionStrategyDependencies dependencies)
9+
=> new YdbExecutionStrategy(dependencies);
10+
}

0 commit comments

Comments
 (0)