Skip to content

Commit d96f824

Browse files
authored
implementation of schema generation (without scaffolding) (#10)
1 parent a7d7b44 commit d96f824

File tree

9 files changed

+359
-27
lines changed

9 files changed

+359
-27
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using EfCore.Ydb.Extensions;
2+
using Microsoft.EntityFrameworkCore.Design;
3+
using Microsoft.Extensions.DependencyInjection;
4+
5+
namespace EfCore.Ydb.Design.Internal;
6+
7+
public class YdbDesignTimeServices : IDesignTimeServices
8+
{
9+
public void ConfigureDesignTimeServices(IServiceCollection serviceCollection)
10+
{
11+
serviceCollection.AddEntityFrameworkYdb();
12+
13+
new EntityFrameworkRelationalDesignServicesBuilder(serviceCollection)
14+
.TryAddCoreServices();
15+
}
16+
}
Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,62 @@
11
using System;
2+
using System.Data;
3+
using System.Diagnostics;
4+
using System.Linq;
25
using System.Threading;
36
using System.Threading.Tasks;
7+
using EfCore.Ydb.Storage.Internal;
8+
using Microsoft.EntityFrameworkCore.Diagnostics;
49
using Microsoft.EntityFrameworkCore.Migrations;
10+
using Microsoft.EntityFrameworkCore.Storage;
11+
using Ydb.Sdk.Ado;
512

613
namespace EfCore.Ydb.Migrations.Internal;
714

8-
// TODO: For migrations only. Not required for mvp
915
public class YdbHistoryRepository : HistoryRepository
1016
{
11-
public YdbHistoryRepository(
12-
HistoryRepositoryDependencies dependencies
13-
) : base(dependencies)
17+
public YdbHistoryRepository(HistoryRepositoryDependencies dependencies) : base(dependencies)
1418
{
1519
}
1620

1721
protected override bool InterpretExistsResult(object? value)
18-
{
19-
throw new NotImplementedException();
20-
}
22+
=> throw new InvalidOperationException("Shouldn't be called");
2123

2224
public override IMigrationsDatabaseLock AcquireDatabaseLock()
2325
{
24-
throw new NotImplementedException();
26+
Dependencies.MigrationsLogger.AcquiringMigrationLock();
27+
return new YdbMigrationDatabaseLock(this, Dependencies.Connection);
2528
}
2629

2730
public override Task<IMigrationsDatabaseLock> AcquireDatabaseLockAsync(
28-
CancellationToken cancellationToken = new CancellationToken())
31+
CancellationToken cancellationToken = default
32+
)
2933
{
3034
throw new NotImplementedException();
3135
}
3236

3337
public override string GetCreateIfNotExistsScript()
38+
=> GetCreateScript().Replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS");
39+
40+
public override LockReleaseBehavior LockReleaseBehavior => LockReleaseBehavior.Transaction;
41+
42+
protected override string ExistsSql
43+
=> throw new UnreachableException("Shouldn't be called. We check if exists using different approach");
44+
45+
public override bool Exists()
46+
=> ExistsAsync().ConfigureAwait(false).GetAwaiter().GetResult();
47+
48+
public override Task<bool> ExistsAsync(CancellationToken cancellationToken = default)
3449
{
35-
throw new NotImplementedException();
50+
var connection = (YdbRelationalConnection)Dependencies.Connection;
51+
var schema = (YdbConnection)connection.DbConnection;
52+
var tables = schema.GetSchema("tables");
53+
54+
var foundTables =
55+
from table in tables.AsEnumerable()
56+
where table.Field<string>("table_type") == "TABLE"
57+
&& table.Field<string>("table_name") == TableName
58+
select table;
59+
return Task.FromResult(foundTables.Count() == 1);
3660
}
3761

3862
public override string GetBeginIfNotExistsScript(string migrationId)
@@ -50,6 +74,27 @@ public override string GetEndIfScript()
5074
throw new NotImplementedException();
5175
}
5276

53-
public override LockReleaseBehavior LockReleaseBehavior { get; }
54-
protected override string ExistsSql { get; }
77+
// TODO Implement lock
78+
private sealed class YdbMigrationDatabaseLock : IMigrationsDatabaseLock
79+
{
80+
private YdbRelationalConnection _connection;
81+
82+
public YdbMigrationDatabaseLock(
83+
IHistoryRepository historyRepository,
84+
IRelationalConnection connection
85+
)
86+
{
87+
HistoryRepository = historyRepository;
88+
_connection = (YdbRelationalConnection)connection;
89+
}
90+
91+
public void Dispose()
92+
{
93+
}
94+
95+
public ValueTask DisposeAsync()
96+
=> default;
97+
98+
public IHistoryRepository HistoryRepository { get; }
99+
}
55100
}
Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,85 @@
1+
using Microsoft.EntityFrameworkCore.Metadata;
12
using Microsoft.EntityFrameworkCore.Migrations;
3+
using Microsoft.EntityFrameworkCore.Migrations.Operations;
24

35
namespace EfCore.Ydb.Migrations;
46

57
public class YdbMigrationsSqlGenerator : MigrationsSqlGenerator
68
{
7-
public YdbMigrationsSqlGenerator(
8-
MigrationsSqlGeneratorDependencies dependencies
9-
) : base(dependencies)
9+
public YdbMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies) : base(dependencies)
1010
{
1111
}
12+
13+
protected override void Generate(
14+
CreateTableOperation operation,
15+
IModel? model,
16+
MigrationCommandListBuilder builder,
17+
bool terminate = true
18+
)
19+
{
20+
if (!terminate && operation.Comment is not null)
21+
{
22+
// TODO: Handle comments
23+
}
24+
25+
builder.Append("CREATE ");
26+
// TODO: Support EXTERNAL tables?
27+
builder
28+
.Append("TABLE ")
29+
.Append(DelimitIdentifier(operation.Name, operation.Schema))
30+
.AppendLine(" (");
31+
32+
using (builder.Indent())
33+
{
34+
CreateTableColumns(operation, model, builder);
35+
CreateTableConstraints(operation, model, builder);
36+
builder.AppendLine();
37+
}
38+
39+
builder.Append(")");
40+
41+
// TODO: Support `WITH {}` block
42+
43+
if (terminate)
44+
{
45+
builder.Append(";");
46+
EndStatement(builder, suppressTransaction: true);
47+
}
48+
}
49+
50+
protected override void CreateTablePrimaryKeyConstraint(
51+
CreateTableOperation operation,
52+
IModel? model,
53+
MigrationCommandListBuilder builder
54+
)
55+
{
56+
if (operation.PrimaryKey == null) return;
57+
58+
builder.AppendLine(",");
59+
PrimaryKeyConstraint(operation.PrimaryKey, model, builder);
60+
}
61+
62+
protected override void PrimaryKeyConstraint(
63+
AddPrimaryKeyOperation operation,
64+
IModel? model,
65+
MigrationCommandListBuilder builder
66+
)
67+
{
68+
builder
69+
.Append("PRIMARY KEY (")
70+
.Append(ColumnList(operation.Columns))
71+
.Append(")");
72+
IndexOptions(operation, model, builder);
73+
}
74+
75+
protected override void Generate(SqlOperation operation, IModel? model, MigrationCommandListBuilder builder)
76+
{
77+
builder.AppendLine(operation.Sql);
78+
// TODO: Find out how to apply migration without suppressing transaction
79+
// We suppress transaction because CREATE/DROP operations cannot be executed during them
80+
EndStatement(builder, suppressTransaction: true);
81+
}
82+
83+
private string DelimitIdentifier(string name, string? schema)
84+
=> Dependencies.SqlGenerationHelper.DelimitIdentifier(name, schema);
1285
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
using Microsoft.EntityFrameworkCore.Design;
2+
3+
[assembly: DesignTimeProviderServices("EfCore.Ydb.Design.Internal.YdbDesignTimeServices")]
Lines changed: 140 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,163 @@
11
using System.Collections.Generic;
2+
using System.Linq;
23
using System.Reflection;
4+
using EfCore.Ydb.Utilities;
35
using Microsoft.EntityFrameworkCore;
46
using Microsoft.EntityFrameworkCore.Diagnostics;
57
using Microsoft.EntityFrameworkCore.Query;
68
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
9+
using Microsoft.EntityFrameworkCore.Storage;
710

811
namespace EfCore.Ydb.Query.Internal.Translators;
912

10-
public class YdbQueryableAggregateMethodTranslator
11-
: IAggregateMethodCallTranslator
13+
public class YdbQueryableAggregateMethodTranslator : IAggregateMethodCallTranslator
1214
{
15+
private readonly YdbSqlExpressionFactory _sqlExpressionFactory;
16+
private readonly IRelationalTypeMappingSource _typeMappingSource;
17+
1318
public YdbQueryableAggregateMethodTranslator(
14-
YdbSqlExpressionFactory sqlExpressionFactory
19+
YdbSqlExpressionFactory sqlExpressionFactory,
20+
IRelationalTypeMappingSource typeMappingSource
1521
)
1622
{
23+
_sqlExpressionFactory = sqlExpressionFactory;
24+
_typeMappingSource = typeMappingSource;
1725
}
1826

1927
public SqlExpression? Translate(
2028
MethodInfo method,
2129
EnumerableExpression source,
2230
IReadOnlyList<SqlExpression> arguments,
23-
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
31+
IDiagnosticsLogger<DbLoggerCategory.Query> logger
32+
)
2433
{
34+
if (method.DeclaringType != typeof(Queryable)) return null;
35+
36+
var methodInfo = method.IsGenericMethod
37+
? method.GetGenericMethodDefinition()
38+
: method;
39+
switch (methodInfo.Name)
40+
{
41+
case nameof(Queryable.Average)
42+
when (QueryableMethods.IsAverageWithoutSelector(methodInfo)
43+
|| QueryableMethods.IsAverageWithSelector(methodInfo))
44+
&& source.Selector is SqlExpression averageSqlExpression:
45+
var averageInputType = averageSqlExpression.Type;
46+
if (averageInputType == typeof(int)
47+
|| averageInputType == typeof(long))
48+
{
49+
averageSqlExpression = _sqlExpressionFactory.ApplyDefaultTypeMapping(
50+
_sqlExpressionFactory.Convert(averageSqlExpression, typeof(double)));
51+
}
52+
53+
return averageInputType == typeof(float)
54+
? _sqlExpressionFactory.Convert(
55+
_sqlExpressionFactory.Function(
56+
"AVG",
57+
[averageSqlExpression],
58+
nullable: true,
59+
argumentsPropagateNullability: ArrayUtil.FalseArrays[1],
60+
returnType: typeof(double)),
61+
averageSqlExpression.Type,
62+
averageSqlExpression.TypeMapping)
63+
: _sqlExpressionFactory.Function(
64+
"AVG",
65+
[averageSqlExpression],
66+
nullable: true,
67+
argumentsPropagateNullability: ArrayUtil.FalseArrays[1],
68+
averageSqlExpression.Type,
69+
averageSqlExpression.TypeMapping);
70+
71+
case nameof(Queryable.Count)
72+
when methodInfo == QueryableMethods.CountWithoutPredicate
73+
|| methodInfo == QueryableMethods.CountWithPredicate:
74+
var countSqlExpression = (source.Selector as SqlExpression) ?? _sqlExpressionFactory.Fragment("*");
75+
return _sqlExpressionFactory.Convert(
76+
_sqlExpressionFactory.Function(
77+
"COUNT",
78+
[countSqlExpression],
79+
nullable: false,
80+
argumentsPropagateNullability: ArrayUtil.FalseArrays[1],
81+
typeof(long)),
82+
typeof(int),
83+
_typeMappingSource.FindMapping(typeof(int)));
84+
85+
case nameof(Queryable.LongCount)
86+
when methodInfo == QueryableMethods.LongCountWithoutPredicate
87+
|| methodInfo == QueryableMethods.LongCountWithPredicate:
88+
var longCountSqlExpression = (source.Selector as SqlExpression) ?? _sqlExpressionFactory.Fragment("*");
89+
return _sqlExpressionFactory.Function(
90+
"COUNT",
91+
[longCountSqlExpression],
92+
nullable: false,
93+
argumentsPropagateNullability: ArrayUtil.FalseArrays[1],
94+
typeof(long));
95+
96+
case nameof(Queryable.Max)
97+
when (methodInfo == QueryableMethods.MaxWithoutSelector
98+
|| methodInfo == QueryableMethods.MaxWithSelector)
99+
&& source.Selector is SqlExpression maxSqlExpression:
100+
return _sqlExpressionFactory.Function(
101+
"MAX",
102+
[maxSqlExpression],
103+
nullable: true,
104+
argumentsPropagateNullability: ArrayUtil.FalseArrays[1],
105+
maxSqlExpression.Type,
106+
maxSqlExpression.TypeMapping);
107+
108+
case nameof(Queryable.Min)
109+
when (methodInfo == QueryableMethods.MinWithoutSelector
110+
|| methodInfo == QueryableMethods.MinWithSelector)
111+
&& source.Selector is SqlExpression minSqlExpression:
112+
return _sqlExpressionFactory.Function(
113+
"MIN",
114+
[minSqlExpression],
115+
nullable: true,
116+
argumentsPropagateNullability: ArrayUtil.FalseArrays[1],
117+
minSqlExpression.Type,
118+
minSqlExpression.TypeMapping);
119+
120+
case nameof(Queryable.Sum)
121+
when (QueryableMethods.IsSumWithoutSelector(methodInfo)
122+
|| QueryableMethods.IsSumWithSelector(methodInfo))
123+
&& source.Selector is SqlExpression sumSqlExpression:
124+
var sumInputType = sumSqlExpression.Type;
125+
126+
if (sumInputType == typeof(int))
127+
{
128+
return _sqlExpressionFactory.Convert(
129+
_sqlExpressionFactory.Function(
130+
"SUM",
131+
[sumSqlExpression],
132+
nullable: true,
133+
argumentsPropagateNullability: ArrayUtil.FalseArrays[1],
134+
typeof(long)),
135+
sumInputType,
136+
sumSqlExpression.TypeMapping);
137+
}
138+
139+
if (sumInputType == typeof(long))
140+
{
141+
return _sqlExpressionFactory.Convert(
142+
_sqlExpressionFactory.Function(
143+
"SUM",
144+
[sumSqlExpression],
145+
nullable: true,
146+
argumentsPropagateNullability: ArrayUtil.FalseArrays[1],
147+
typeof(decimal)),
148+
sumInputType,
149+
sumSqlExpression.TypeMapping);
150+
}
151+
152+
return _sqlExpressionFactory.Function(
153+
"SUM",
154+
[sumSqlExpression],
155+
nullable: true,
156+
argumentsPropagateNullability: ArrayUtil.FalseArrays[1],
157+
sumInputType,
158+
sumSqlExpression.TypeMapping);
159+
}
160+
25161
return null;
26162
}
27163
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ namespace EfCore.Ydb.Storage.Internal;
44

55
public interface IYdbRelationalConnection : IRelationalConnection
66
{
7+
8+
IYdbRelationalConnection Clone();
79
}

0 commit comments

Comments
 (0)