Skip to content

Commit dd5de8b

Browse files
EFCore: Enable MigrationSqlGeneratorTests (#295)
1 parent 7b78dbd commit dd5de8b

26 files changed

+594
-183
lines changed

.github/workflows/tests.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,31 @@ jobs:
9595
dotnet-version: ${{ matrix.dotnet-version }}
9696
- name: Run Topic tests
9797
run: dotnet test src/Ydb.Sdk/tests/Tests.csproj --filter "FullyQualifiedName~Topic" -f ${{ matrix.dotnet-target-framework }} -l "console;verbosity=detailed"
98+
efcore-functional-tests:
99+
runs-on: ubuntu-22.04
100+
strategy:
101+
fail-fast: false
102+
matrix:
103+
ydb-version: [ 'latest', 'trunk' ]
104+
services:
105+
ydb:
106+
image: ydbplatform/local-ydb:${{ matrix.ydb-version }}
107+
ports:
108+
- 2135:2135
109+
- 2136:2136
110+
- 8765:8765
111+
env:
112+
YDB_LOCAL_SURVIVE_RESTART: true
113+
options: '--name ydb-local -h localhost'
114+
steps:
115+
- name: Checkout code
116+
uses: actions/checkout@v4
117+
- name: Install Dotnet
118+
uses: actions/setup-dotnet@v4
119+
with:
120+
dotnet-version: 9.0.x
121+
- name: Run EfCore tests
122+
run: dotnet test src/EfCore.Ydb/test/EfCore.Ydb.FunctionalTests/EfCore.Ydb.FunctionalTests.csproj -l "console;verbosity=detailed"
98123
integration-tests:
99124
runs-on: ubuntu-22.04
100125
strategy:

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

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
namespace EfCore.Ydb.Migrations;
99

10-
// ReSharper disable once ClassNeverInstantiated.Global
1110
public class YdbMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies)
1211
: MigrationsSqlGenerator(dependencies)
1312
{
@@ -23,10 +22,7 @@ protected override void Generate(
2322
// TODO: Handle comments
2423
}
2524

26-
builder.Append("CREATE ");
27-
// TODO: Support EXTERNAL tables?
28-
builder
29-
.Append("TABLE ")
25+
builder.Append("CREATE TABLE ")
3026
.Append(DelimitIdentifier(operation.Name, operation.Schema))
3127
.AppendLine(" (");
3228

@@ -66,9 +62,10 @@ MigrationCommandListBuilder builder
6662
{
6763
columnType = columnType.ToLower() switch
6864
{
65+
"int8" or "int16" => "SmallSerial",
6966
"int32" => "Serial",
7067
"int64" => "Bigserial",
71-
_ => throw new NotSupportedException("Serial column supported only for int32 and int64")
68+
_ => throw new NotSupportedException($"Serial column isn't supported for {columnType} type")
7269
};
7370
}
7471

@@ -77,7 +74,7 @@ MigrationCommandListBuilder builder
7774
.Append(" ")
7875
// TODO: Add DEFAULT logic somewhere here
7976
.Append(columnType)
80-
.Append(operation.IsNullable ? " NULL" : " NOT NULL");
77+
.Append(operation.IsNullable ? string.Empty : " NOT NULL");
8178
}
8279

8380
protected override void CreateTablePrimaryKeyConstraint(
@@ -109,7 +106,7 @@ protected override void Generate(RenameTableOperation operation, IModel? model,
109106
{
110107
if (operation.NewSchema is not null && operation.NewSchema != operation.Schema)
111108
{
112-
throw new NotImplementedException("Rename table with schema is not supported");
109+
throw new NotSupportedException("Rename table with schema is not supported");
113110
}
114111

115112
if (operation.NewName is null || operation.NewName == operation.Name)
@@ -120,22 +117,23 @@ protected override void Generate(RenameTableOperation operation, IModel? model,
120117
builder
121118
.Append("ALTER TABLE ")
122119
.Append(DelimitIdentifier(operation.Name, operation.Schema))
123-
.AppendLine("RENAME TO")
124-
.Append(DelimitIdentifier(operation.NewName, operation.Schema));
120+
.Append(" RENAME TO ")
121+
.Append(DelimitIdentifier(operation.NewName, operation.Schema))
122+
.AppendLine(";");
125123
EndStatement(builder);
126124
}
127125

128126
protected override void Generate(
129-
InsertDataOperation operation, IModel? model, MigrationCommandListBuilder builder, bool terminate = true
127+
InsertDataOperation operation,
128+
IModel? model,
129+
MigrationCommandListBuilder builder,
130+
bool terminate = true
130131
)
131132
{
132133
var sqlBuilder = new StringBuilder();
133134
foreach (var modificationCommand in GenerateModificationCommands(operation, model))
134135
{
135-
SqlGenerator.AppendInsertOperation(
136-
sqlBuilder,
137-
modificationCommand,
138-
0);
136+
SqlGenerator.AppendInsertOperation(sqlBuilder, modificationCommand, 0);
139137
}
140138

141139
builder.Append(sqlBuilder.ToString());

src/EfCore.Ydb/src/Storage/Internal/Mapping/YdbBoolTypeMapping.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ public class YdbBoolTypeMapping : BoolTypeMapping
66
{
77
public new static YdbBoolTypeMapping Default { get; } = new();
88

9-
private YdbBoolTypeMapping() : base("BOOL")
9+
private YdbBoolTypeMapping() : base("Bool")
1010
{
1111
}
1212

@@ -19,5 +19,5 @@ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters p
1919
=> new YdbBoolTypeMapping(parameters);
2020

2121
protected override string GenerateNonNullSqlLiteral(object value)
22-
=> (bool)value ? "true" : "false";
22+
=> (bool)value ? "TRUE" : "FALSE";
2323
}
Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
using System;
2-
using System.Data.Common;
3-
using System.Reflection;
42
using Microsoft.EntityFrameworkCore.Storage;
53

64
namespace EfCore.Ydb.Storage.Internal.Mapping;
75

8-
public class YdbDecimalTypeMapping : RelationalTypeMapping
6+
public class YdbDecimalTypeMapping : DecimalTypeMapping
97
{
8+
private const byte DefaultPrecision = 22;
9+
private const byte DefaultScale = 9;
10+
1011
public YdbDecimalTypeMapping(Type? type) : this(
1112
new RelationalTypeMappingParameters(
1213
new CoreTypeMappingParameters(type ?? typeof(decimal)),
@@ -26,14 +27,5 @@ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters p
2627

2728
protected override string ProcessStoreType(
2829
RelationalTypeMappingParameters parameters, string storeType, string storeTypeNameBase
29-
) => storeType == "BigInteger" && parameters.Precision != null
30-
? $"Decimal({parameters.Precision}, 0)"
31-
: parameters.Precision is null
32-
? storeType
33-
: parameters.Scale is null
34-
? $"Decimal({parameters.Precision}, 0)"
35-
: $"Decimal({parameters.Precision}, {parameters.Scale})";
36-
37-
public override MethodInfo GetDataReaderMethod() =>
38-
typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.GetDecimal), [typeof(int)]) ?? throw new Exception();
30+
) => $"{storeType}({parameters.Precision ?? DefaultPrecision}, {parameters.Scale ?? DefaultScale})";
3931
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,24 @@ public override void DelimitIdentifier(StringBuilder builder, string identifier)
1010
builder.Append('`').Append(identifier).Append('`');
1111

1212
public override string DelimitIdentifier(string identifier) => $"`{identifier}`";
13+
14+
public override string EscapeIdentifier(string identifier) => identifier.Replace("`", "``");
15+
16+
public override void EscapeIdentifier(StringBuilder builder, string identifier)
17+
{
18+
var length = builder.Length;
19+
builder.Append(identifier);
20+
builder.Replace("`", "``", length, identifier.Length);
21+
}
22+
23+
public override string DelimitIdentifier(string name, string? schema) =>
24+
DelimitIdentifier(
25+
(!string.IsNullOrEmpty(schema) ? schema + "/" : string.Empty)
26+
+ name
27+
);
28+
29+
public override void DelimitIdentifier(StringBuilder builder, string name, string? schema) =>
30+
builder.Append(
31+
DelimitIdentifier((!string.IsNullOrEmpty(schema) ? schema + "/" : string.Empty) + name)
32+
);
1333
}

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

Lines changed: 68 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,114 @@
11
using System;
2-
using System.Collections.Concurrent;
32
using System.Collections.Generic;
43
using System.Data;
5-
using System.Numerics;
64
using System.Text.Json;
75
using EfCore.Ydb.Storage.Internal.Mapping;
86
using Microsoft.EntityFrameworkCore.Storage;
97
using Type = System.Type;
108

119
namespace EfCore.Ydb.Storage.Internal;
1210

13-
public sealed class YdbTypeMappingSource : RelationalTypeMappingSource
11+
public sealed class YdbTypeMappingSource(
12+
TypeMappingSourceDependencies dependencies,
13+
RelationalTypeMappingSourceDependencies relationalDependencies
14+
) : RelationalTypeMappingSource(dependencies, relationalDependencies)
1415
{
15-
private ConcurrentDictionary<string, RelationalTypeMapping[]> StoreTypeMapping { get; }
16-
private ConcurrentDictionary<Type, RelationalTypeMapping> ClrTypeMapping { get; }
17-
1816
#region Mappings
1917

20-
private readonly YdbBoolTypeMapping _bool = YdbBoolTypeMapping.Default;
18+
private static readonly YdbBoolTypeMapping Bool = YdbBoolTypeMapping.Default;
19+
20+
private static readonly SByteTypeMapping Int8 = new("Int8", DbType.SByte);
21+
private static readonly ShortTypeMapping Int16 = new("Int16", DbType.Int16);
22+
private static readonly IntTypeMapping Int32 = new("Int32", DbType.Int32);
23+
private static readonly LongTypeMapping Int64 = new("Int64", DbType.Int64);
2124

22-
private readonly SByteTypeMapping _int8 = new("Int8", DbType.SByte);
23-
private readonly ShortTypeMapping _int16 = new("Int16", DbType.Int16);
24-
private readonly IntTypeMapping _int32 = new("Int32", DbType.Int32);
25-
private readonly LongTypeMapping _int64 = new("Int64", DbType.Int64);
25+
private static readonly ByteTypeMapping Uint8 = new("Uint8", DbType.Byte);
26+
private static readonly UShortTypeMapping Uint16 = new("Uint16", DbType.UInt16);
27+
private static readonly UIntTypeMapping Uint32 = new("Uint32", DbType.UInt32);
28+
private static readonly ULongTypeMapping Uint64 = new("Uint64", DbType.UInt64);
2629

27-
private readonly ByteTypeMapping _uint8 = new("Uint8", DbType.Byte);
28-
private readonly UShortTypeMapping _uint16 = new("Uint16", DbType.UInt16);
29-
private readonly UIntTypeMapping _uint32 = new("Uint32", DbType.UInt32);
30-
private readonly ULongTypeMapping _uint64 = new("Uint64", DbType.UInt64);
30+
private static readonly FloatTypeMapping Float = new("Float", DbType.Single);
31+
private static readonly DoubleTypeMapping Double = new("Double", DbType.Double);
3132

32-
private readonly FloatTypeMapping _float = new("Float", DbType.Single);
33-
private readonly DoubleTypeMapping _double = new("Double", DbType.Double);
34-
private readonly YdbDecimalTypeMapping _biginteger = new(typeof(BigInteger));
35-
private readonly YdbDecimalTypeMapping _decimal = new(typeof(decimal));
36-
private readonly YdbDecimalTypeMapping _decimalAsDouble = new(typeof(double));
37-
private readonly YdbDecimalTypeMapping _decimalAsFloat = new(typeof(float));
33+
private static readonly YdbDecimalTypeMapping Decimal = new(typeof(decimal));
3834

39-
private readonly StringTypeMapping _text = new("Text", DbType.String);
40-
private readonly YdbStringTypeMapping _ydbString = YdbStringTypeMapping.Default;
41-
private readonly YdbBytesTypeMapping _bytes = YdbBytesTypeMapping.Default;
42-
private readonly YdbJsonTypeMapping _json = new("Json", typeof(JsonElement), DbType.String);
35+
private static readonly StringTypeMapping Text = new("Text", DbType.String);
36+
private static readonly YdbBytesTypeMapping Bytes = YdbBytesTypeMapping.Default;
37+
private static readonly YdbJsonTypeMapping Json = new("Json", typeof(JsonElement), DbType.String);
4338

44-
private readonly DateOnlyTypeMapping _date = new("Date");
45-
private readonly DateTimeTypeMapping _dateTime = new("Datetime");
46-
private readonly DateTimeTypeMapping _timestamp = new("Timestamp");
47-
private readonly TimeSpanTypeMapping _interval = new("Interval");
39+
private static readonly DateOnlyTypeMapping Date = new("Date");
40+
private static readonly DateTimeTypeMapping DateTime = new("Datetime");
41+
private static readonly DateTimeTypeMapping Timestamp = new("Timestamp");
42+
private static readonly TimeSpanTypeMapping Interval = new("Interval");
4843

4944
#endregion
5045

51-
public YdbTypeMappingSource(
52-
TypeMappingSourceDependencies dependencies,
53-
RelationalTypeMappingSourceDependencies relationalDependencies
54-
) : base(dependencies, relationalDependencies)
55-
{
56-
var storeTypeMappings = new Dictionary<string, RelationalTypeMapping[]>(StringComparer.OrdinalIgnoreCase)
46+
private static readonly Dictionary<string, RelationalTypeMapping[]> StoreTypeMapping =
47+
new(StringComparer.OrdinalIgnoreCase)
5748
{
58-
{ "Bool", [_bool] },
49+
{ "Bool", [Bool] },
5950

60-
{ "Int8", [_int8] },
61-
{ "Int16", [_int16] },
62-
{ "Int32", [_int32] },
63-
{ "Int64", [_int64] },
51+
{ "Int8", [Int8] },
52+
{ "Int16", [Int16] },
53+
{ "Int32", [Int32] },
54+
{ "Int64", [Int64] },
6455

65-
{ "Uint8", [_uint8] },
66-
{ "Uint16", [_uint16] },
67-
{ "Uint32", [_uint32] },
68-
{ "Uint64", [_uint64] },
56+
{ "Uint8", [Uint8] },
57+
{ "Uint16", [Uint16] },
58+
{ "Uint32", [Uint32] },
59+
{ "Uint64", [Uint64] },
6960

70-
{ "Float", [_float] },
71-
{ "Double", [_double] },
61+
{ "Float", [Float] },
62+
{ "Double", [Double] },
7263

73-
{ "Decimal", [_decimal, _decimalAsDouble, _decimalAsFloat, _biginteger] },
64+
{ "Date", [Date] },
65+
{ "DateTime", [DateTime] },
66+
{ "Timestamp", [Timestamp] },
67+
{ "Interval", [Interval] },
7468

75-
{ "Text", [_text] },
76-
{ "String", [_ydbString] },
77-
{ "Json", [_json] },
69+
{ "Text", [Text] },
70+
{ "Bytes", [Bytes] },
7871

79-
{ "Bytes", [_bytes] },
72+
{ "Decimal", [Decimal] },
8073

81-
{ "Date", [_date] },
82-
{ "DateTime", [_dateTime] },
83-
{ "Timestamp", [_timestamp] },
84-
{ "Interval", [_interval] }
74+
{ "Json", [Json] }
8575
};
86-
var clrTypeMappings = new Dictionary<Type, RelationalTypeMapping>
87-
{
88-
{ typeof(bool), _bool },
8976

90-
{ typeof(sbyte), _int8 },
91-
{ typeof(short), _int16 },
92-
{ typeof(int), _int32 },
93-
{ typeof(long), _int64 },
77+
private static readonly Dictionary<Type, RelationalTypeMapping> ClrTypeMapping = new()
78+
{
79+
{ typeof(bool), Bool },
9480

95-
{ typeof(byte), _uint8 },
96-
{ typeof(ushort), _uint16 },
97-
{ typeof(uint), _uint32 },
98-
{ typeof(ulong), _uint64 },
81+
{ typeof(sbyte), Int8 },
82+
{ typeof(short), Int16 },
83+
{ typeof(int), Int32 },
84+
{ typeof(long), Int64 },
9985

100-
{ typeof(float), _float },
101-
{ typeof(double), _double },
102-
{ typeof(decimal), _decimal },
86+
{ typeof(byte), Uint8 },
87+
{ typeof(ushort), Uint16 },
88+
{ typeof(uint), Uint32 },
89+
{ typeof(ulong), Uint64 },
10390

104-
{ typeof(string), _text },
105-
{ typeof(byte[]), _bytes },
106-
{ typeof(JsonElement), _json },
91+
{ typeof(float), Float },
92+
{ typeof(double), Double },
93+
{ typeof(decimal), Decimal },
10794

108-
{ typeof(DateOnly), _date },
109-
{ typeof(DateTime), _timestamp },
110-
{ typeof(TimeSpan), _interval }
111-
};
95+
{ typeof(string), Text },
96+
{ typeof(byte[]), Bytes },
97+
{ typeof(JsonElement), Json },
11298

113-
StoreTypeMapping = new ConcurrentDictionary<string, RelationalTypeMapping[]>(storeTypeMappings);
114-
ClrTypeMapping = new ConcurrentDictionary<Type, RelationalTypeMapping>(clrTypeMappings);
115-
}
99+
{ typeof(DateOnly), Date },
100+
{ typeof(DateTime), Timestamp },
101+
{ typeof(TimeSpan), Interval }
102+
};
116103

117104
protected override RelationalTypeMapping? FindMapping(in RelationalTypeMappingInfo mappingInfo)
118-
=> FindBaseMapping(mappingInfo)
119-
?? base.FindMapping(mappingInfo);
105+
=> base.FindMapping(mappingInfo) ?? FindBaseMapping(mappingInfo)?.Clone(mappingInfo);
120106

121-
private RelationalTypeMapping? FindBaseMapping(in RelationalTypeMappingInfo mappingInfo)
107+
private static RelationalTypeMapping? FindBaseMapping(in RelationalTypeMappingInfo mappingInfo)
122108
{
123109
var clrType = mappingInfo.ClrType;
124110
var storeTypeName = mappingInfo.StoreTypeName;
125111

126-
// Special case.
127-
// If property has [YdbString] attribute then we use STRING type instead of TEXT
128-
if (mappingInfo.StoreTypeName == "string")
129-
{
130-
return _ydbString;
131-
}
132-
133112
if (storeTypeName is null)
134113
{
135114
return clrType is null ? null : ClrTypeMapping.GetValueOrDefault(clrType);

0 commit comments

Comments
 (0)