Skip to content

Commit d5b37d7

Browse files
committed
added support for delete and update queries
1 parent 826612f commit d5b37d7

File tree

8 files changed

+426
-15
lines changed

8 files changed

+426
-15
lines changed

src/EfCore.Ydb/src/Extensions/YdbServiceCollectionExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public static IServiceCollection AddEntityFrameworkYdb(this IServiceCollection s
3434
.TryAdd<IProviderConventionSetBuilder, YdbConventionSetBuilder>()
3535
.TryAdd<IUpdateSqlGenerator, YdbUpdateSqlGenerator>()
3636
.TryAdd<IModificationCommandFactory, YdbModificationCommandFactory>()
37+
.TryAdd<IRelationalTransactionFactory, YdbRelationalTransactionFactory>()
3738
.TryAdd<IModificationCommandBatchFactory, YdbModificationCommandBatchFactory>()
3839
.TryAdd<IRelationalConnection>(p => p.GetRequiredService<IYdbRelationalConnection>())
3940
.TryAdd<IMigrationsSqlGenerator, YdbMigrationsSqlGenerator>()

src/EfCore.Ydb/src/Migrations/YdbMigrationsSqlGenerator.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,42 @@ protected override void Generate(
214214
// Ignore bc YDB doesn't support adding keys outside table creation
215215
}
216216

217+
protected override void CreateTableForeignKeys(CreateTableOperation operation, IModel? model, MigrationCommandListBuilder builder)
218+
{
219+
// Same comment about Foreign keys
220+
}
221+
222+
protected override void ForeignKeyAction(ReferentialAction referentialAction, MigrationCommandListBuilder builder)
223+
{
224+
// Same comment about Foreign keys
225+
}
226+
227+
protected override void ForeignKeyConstraint(AddForeignKeyOperation operation, IModel? model, MigrationCommandListBuilder builder)
228+
{
229+
// Same comment about Foreign keys
230+
}
231+
232+
protected override void CreateTableUniqueConstraints(CreateTableOperation operation, IModel? model, MigrationCommandListBuilder builder)
233+
{
234+
// We don't have unique constraints
235+
}
236+
237+
protected override void UniqueConstraint(AddUniqueConstraintOperation operation, IModel? model, MigrationCommandListBuilder builder)
238+
{
239+
// Same comment about Unique constraints
240+
}
241+
242+
protected override void Generate(
243+
CreateIndexOperation operation,
244+
IModel? model,
245+
MigrationCommandListBuilder builder,
246+
bool terminate = true
247+
)
248+
{
249+
// TODO: We do have Indexes!
250+
// But they're not implemented yet. Ignoring indexes because otherwise table generation during tests will fail
251+
}
252+
217253

218254
// ReSharper disable once RedundantOverriddenMember
219255
protected override void EndStatement(
Lines changed: 220 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,228 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Linq.Expressions;
14
using Microsoft.EntityFrameworkCore.Query;
5+
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
6+
using Microsoft.EntityFrameworkCore.Storage;
27

38
namespace EfCore.Ydb.Query.Internal;
49

5-
public class YdbQuerySqlGenerator
6-
: QuerySqlGenerator
10+
public class YdbQuerySqlGenerator : QuerySqlGenerator
711
{
8-
public YdbQuerySqlGenerator(
9-
QuerySqlGeneratorDependencies dependencies
10-
) : base(dependencies)
12+
protected readonly ISqlGenerationHelper SqlGenerationHelper;
13+
protected bool SkipAliases;
14+
15+
public YdbQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies) : base(dependencies)
16+
{
17+
SqlGenerationHelper = dependencies.SqlGenerationHelper;
18+
}
19+
20+
protected override Expression VisitColumn(ColumnExpression columnExpression)
21+
{
22+
if (SkipAliases)
23+
{
24+
Sql.Append(SqlGenerationHelper.DelimitIdentifier(columnExpression.Name));
25+
}
26+
else
27+
{
28+
base.VisitColumn(columnExpression);
29+
}
30+
31+
return columnExpression;
32+
}
33+
34+
protected override Expression VisitTable(TableExpression tableExpression)
35+
{
36+
if (SkipAliases)
37+
{
38+
Sql.Append(SqlGenerationHelper.DelimitIdentifier(tableExpression.Name, tableExpression.Schema));
39+
}
40+
else
41+
{
42+
base.VisitTable(tableExpression);
43+
}
44+
45+
return tableExpression;
46+
}
47+
48+
protected override Expression VisitDelete(DeleteExpression deleteExpression)
49+
{
50+
Sql.Append("DELETE FROM ");
51+
52+
SkipAliases = true;
53+
Visit(deleteExpression.Table);
54+
SkipAliases = false;
55+
56+
var select = deleteExpression.SelectExpression;
57+
58+
var complexSelect = IsComplexSelect(deleteExpression.SelectExpression, deleteExpression.Table);
59+
60+
if (select.Predicate is InExpression predicate)
61+
{
62+
Sql.Append(" ON ");
63+
Visit(predicate.Subquery);
64+
}
65+
else if (!complexSelect)
66+
{
67+
GenerateSimpleWhere(select, skipAliases: true);
68+
}
69+
else
70+
{
71+
// I'm not sure if this always work.
72+
// But for now I didn't find test where it fails
73+
GenerateOnSubquery(null, select);
74+
}
75+
76+
return deleteExpression;
77+
}
78+
79+
protected override Expression VisitUpdate(UpdateExpression updateExpression)
80+
{
81+
Sql.Append("UPDATE ");
82+
83+
SkipAliases = true;
84+
Visit(updateExpression.Table);
85+
SkipAliases = false;
86+
87+
var select = updateExpression.SelectExpression;
88+
89+
var complexSelect = IsComplexSelect(updateExpression.SelectExpression, updateExpression.Table);
90+
91+
if (!complexSelect)
92+
{
93+
GenerateUpdateColumnSetters(updateExpression);
94+
GenerateSimpleWhere(select, skipAliases: true);
95+
}
96+
else
97+
{
98+
// I'm not sure if this always work.
99+
// But for now I didn't find test where it fails
100+
GenerateOnSubquery(updateExpression.ColumnValueSetters, select);
101+
}
102+
103+
return updateExpression;
104+
}
105+
106+
private void GenerateSimpleWhere(SelectExpression select, bool skipAliases)
107+
{
108+
var predicate = select.Predicate;
109+
if (predicate == null) return;
110+
111+
Sql.AppendLine().Append("WHERE ");
112+
if (skipAliases) SkipAliases = true;
113+
Visit(predicate);
114+
if (skipAliases) SkipAliases = false;
115+
}
116+
117+
private void GenerateUpdateColumnSetters(UpdateExpression updateExpression)
118+
{
119+
Sql.AppendLine()
120+
.Append("SET ")
121+
.Append(SqlGenerationHelper.DelimitIdentifier(updateExpression.ColumnValueSetters[0].Column.Name))
122+
.Append(" = ");
123+
124+
SkipAliases = true;
125+
Visit(updateExpression.ColumnValueSetters[0].Value);
126+
SkipAliases = false;
127+
128+
using (Sql.Indent())
129+
{
130+
foreach (var columnValueSetter in updateExpression.ColumnValueSetters.Skip(1))
131+
{
132+
Sql
133+
.AppendLine(",")
134+
.Append($"{SqlGenerationHelper.DelimitIdentifier(columnValueSetter.Column.Name)} = ");
135+
SkipAliases = true;
136+
Visit(columnValueSetter.Value);
137+
SkipAliases = false;
138+
}
139+
}
140+
}
141+
142+
protected void GenerateOnSubquery(
143+
IReadOnlyList<ColumnValueSetter>? columnValueSetters,
144+
SelectExpression select
145+
)
146+
{
147+
Sql.Append(" ON ").AppendLine().Append("SELECT ");
148+
149+
if (columnValueSetters == null)
150+
{
151+
Sql.Append(" * ");
152+
}
153+
else
154+
{
155+
var columnName = columnValueSetters[0].Column.Name;
156+
Visit(columnValueSetters[0].Value);
157+
Sql.Append(" AS ").Append(columnName);
158+
159+
foreach (var columnValueSetter in columnValueSetters.Skip(1))
160+
{
161+
Sql.Append(", ");
162+
Visit(columnValueSetter.Value);
163+
Sql.Append(" AS ").Append(columnValueSetter.Column.Name);
164+
}
165+
}
166+
167+
if (!TryGenerateWithoutWrappingSelect(select))
168+
{
169+
GenerateFrom(select);
170+
if (select.Predicate != null)
171+
{
172+
Sql.AppendLine().Append("WHERE ");
173+
Visit(select.Predicate);
174+
}
175+
176+
GenerateOrderings(select);
177+
GenerateLimitOffset(select);
178+
}
179+
180+
if (select.Alias != null)
181+
{
182+
Sql.AppendLine()
183+
.Append(")")
184+
.Append(AliasSeparator)
185+
.Append(SqlGenerationHelper.DelimitIdentifier(select.Alias));
186+
}
187+
}
188+
189+
protected override void GenerateLimitOffset(SelectExpression selectExpression)
190+
{
191+
if (selectExpression.Limit == null && selectExpression.Offset == null) return;
192+
193+
Sql.AppendLine().Append("LIMIT ");
194+
if (selectExpression.Limit != null)
195+
{
196+
Visit(selectExpression.Limit);
197+
}
198+
else
199+
{
200+
// We must specify number here because offset without limit leads to exception
201+
Sql.Append(ulong.MaxValue.ToString());
202+
}
203+
204+
if (selectExpression.Offset != null)
205+
{
206+
Sql.Append(" OFFSET ");
207+
Visit(selectExpression.Offset);
208+
}
209+
}
210+
211+
private bool IsComplexSelect(
212+
SelectExpression select,
213+
TableExpressionBase fromTable
214+
)
11215
{
216+
return select.Offset != null
217+
|| select.Limit != null
218+
|| select.Having != null
219+
|| select.Orderings.Count > 0
220+
|| select.GroupBy.Count > 0
221+
|| select.Projection.Count > 0
222+
|| select.Tables.Count > 1
223+
|| select.Predicate is InExpression
224+
|| !(select.Tables.Count == 1
225+
&& select.Tables[0].Equals(fromTable)
226+
);
12227
}
13228
}

src/EfCore.Ydb/src/Storage/Internal/YdbDatabaseCreator.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ private async Task<bool> ExistsInternal(CancellationToken cancellationToken = de
3434
var connection = _connection.Clone();
3535
try
3636
{
37-
await connection.OpenAsync(cancellationToken, errorsExpected: true);
37+
await _connection.OpenAsync(cancellationToken, errorsExpected: true);
3838
return true;
3939
}
4040
catch (YdbException)
@@ -50,7 +50,8 @@ private async Task<bool> ExistsInternal(CancellationToken cancellationToken = de
5050

5151
public override bool HasTables()
5252
{
53-
throw new NotImplementedException();
53+
// TODO: Implement later
54+
return false;
5455
}
5556

5657
public override void Create()
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System;
2+
using System.Data.Common;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using Microsoft.EntityFrameworkCore;
6+
using Microsoft.EntityFrameworkCore.Diagnostics;
7+
using Microsoft.EntityFrameworkCore.Storage;
8+
9+
namespace EfCore.Ydb.Storage.Internal;
10+
11+
public class YdbRelationalTransaction : RelationalTransaction
12+
{
13+
public YdbRelationalTransaction(IRelationalConnection connection, DbTransaction transaction, Guid transactionId, IDiagnosticsLogger<DbLoggerCategory.Database.Transaction> logger, bool transactionOwned, ISqlGenerationHelper sqlGenerationHelper) : base(connection, transaction, transactionId, logger, transactionOwned, sqlGenerationHelper)
14+
{
15+
}
16+
17+
public override bool SupportsSavepoints
18+
=> false;
19+
20+
public override void CreateSavepoint(string name)
21+
=> throw new NotSupportedException("Savepoints are not supported in YDB");
22+
23+
public override Task CreateSavepointAsync(string name, CancellationToken cancellationToken = new())
24+
=> throw new NotSupportedException("Savepoints are not supported in YDB");
25+
26+
public override void RollbackToSavepoint(string name)
27+
=> throw new NotSupportedException("Savepoints are not supported in YDB");
28+
29+
public override Task RollbackToSavepointAsync(string name, CancellationToken cancellationToken = new())
30+
=> throw new NotSupportedException("Savepoints are not supported in YDB");
31+
32+
public override void ReleaseSavepoint(string name)
33+
=> throw new NotSupportedException("Savepoints are not supported in YDB");
34+
35+
public override Task ReleaseSavepointAsync(string name, CancellationToken cancellationToken = new())
36+
=> throw new NotSupportedException("Savepoints are not supported in YDB");
37+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
using System.Data.Common;
3+
using Microsoft.EntityFrameworkCore;
4+
using Microsoft.EntityFrameworkCore.Diagnostics;
5+
using Microsoft.EntityFrameworkCore.Storage;
6+
7+
namespace EfCore.Ydb.Storage.Internal;
8+
9+
public class YdbRelationalTransactionFactory : IRelationalTransactionFactory
10+
{
11+
public YdbRelationalTransactionFactory(RelationalTransactionFactoryDependencies dependencies)
12+
=> Dependencies = dependencies;
13+
14+
protected virtual RelationalTransactionFactoryDependencies Dependencies { get; }
15+
16+
public RelationalTransaction Create(
17+
IRelationalConnection connection,
18+
DbTransaction transaction,
19+
Guid transactionId,
20+
IDiagnosticsLogger<DbLoggerCategory.Database.Transaction> logger,
21+
bool transactionOwned
22+
) => new YdbRelationalTransaction(
23+
connection,
24+
transaction,
25+
transactionId,
26+
logger,
27+
transactionOwned,
28+
Dependencies.SqlGenerationHelper
29+
);
30+
}

0 commit comments

Comments
 (0)