Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/EFCore.Ydb/src/EntityFrameworkCore.Ydb.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Ydb.Sdk" Version="0.24.0"/>
<PackageReference Include="Ydb.Sdk" Version="0.25.1"/>
</ItemGroup>

<ItemGroup>
Expand Down
11 changes: 11 additions & 0 deletions src/EFCore.Ydb/src/Storage/Internal/Mapping/IYdbTypeMapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Ydb.Sdk.Ado.YdbType;

namespace EntityFrameworkCore.Ydb.Storage.Internal.Mapping;

internal interface IYdbTypeMapping
{
/// <summary>
/// The database type used by YDB.
/// </summary>
YdbDbType YdbDbType { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ private YdbBoolTypeMapping(RelationalTypeMappingParameters parameters)
{
}

protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new YdbBoolTypeMapping(parameters);
protected override YdbBoolTypeMapping Clone(RelationalTypeMappingParameters parameters) => new(parameters);

protected override string GenerateNonNullSqlLiteral(object value)
=> (bool)value ? "TRUE" : "FALSE";
Expand Down
16 changes: 3 additions & 13 deletions src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbBytesTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,16 @@ public class YdbBytesTypeMapping : RelationalTypeMapping
{
public static YdbBytesTypeMapping Default { get; } = new();

private YdbBytesTypeMapping() : base(
new RelationalTypeMappingParameters(
new CoreTypeMappingParameters(
typeof(byte[]),
jsonValueReaderWriter: JsonByteArrayReaderWriter.Instance
),
storeType: "Bytes",
dbType: System.Data.DbType.Binary,
unicode: false
)
)
private YdbBytesTypeMapping() : base("Bytes", typeof(byte[]), System.Data.DbType.Binary,
jsonValueReaderWriter: JsonByteArrayReaderWriter.Instance)
{
}

protected YdbBytesTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters)
{
}

protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new YdbBytesTypeMapping(parameters);
protected override YdbBytesTypeMapping Clone(RelationalTypeMappingParameters parameters) => new(parameters);

protected override string GenerateNonNullSqlLiteral(object value)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@ protected YdbDateOnlyTypeMapping(RelationalTypeMappingParameters parameters) : b
{
}

protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new YdbDateOnlyTypeMapping(parameters);
protected override YdbDateOnlyTypeMapping Clone(RelationalTypeMappingParameters parameters) => new(parameters);

protected override string GenerateNonNullSqlLiteral(object value)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
using System.Text;
using System.Text.Json;
using Microsoft.EntityFrameworkCore.Storage;
using Ydb.Sdk.Ado;
using Ydb.Sdk.Ado.YdbType;

namespace EntityFrameworkCore.Ydb.Storage.Internal.Mapping;

public class YdbJsonTypeMapping : JsonTypeMapping
public class YdbJsonTypeMapping : JsonTypeMapping, IYdbTypeMapping
{
public YdbJsonTypeMapping(string storeType, Type clrType, DbType? dbType) : base(storeType, clrType, dbType)
{
Expand All @@ -32,8 +34,7 @@ private static readonly MethodInfo? EncodingGetBytesMethod
private static readonly ConstructorInfo? MemoryStreamConstructor
= typeof(MemoryStream).GetConstructor([typeof(byte[])]);

protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new YdbJsonTypeMapping(parameters);
protected override YdbJsonTypeMapping Clone(RelationalTypeMappingParameters parameters) => new(parameters);

public override MethodInfo GetDataReaderMethod() => GetStringMethod;

Expand Down Expand Up @@ -72,4 +73,9 @@ public override Expression CustomizeDataReaderExpression(Expression expression)
EncodingGetBytesMethod ?? throw new Exception(),
expression)
);

public YdbDbType YdbDbType => YdbDbType.Json;

protected override void ConfigureParameter(DbParameter parameter) =>
((YdbParameter)parameter).YdbDbType = YdbDbType;
}
19 changes: 19 additions & 0 deletions src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbListTypeMapping.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Collections;
using System.Data.Common;
using Microsoft.EntityFrameworkCore.Storage;
using Ydb.Sdk.Ado;
using Ydb.Sdk.Ado.YdbType;

namespace EntityFrameworkCore.Ydb.Storage.Internal.Mapping;

internal class YdbListTypeMapping(
YdbDbType ydbDbType,
string storeTypeElement
) : RelationalTypeMapping(storeType: $"List<{storeTypeElement}>", typeof(IList))
{
protected override YdbListTypeMapping Clone(RelationalTypeMappingParameters parameters) =>
new(ydbDbType, storeTypeElement);

protected override void ConfigureParameter(DbParameter parameter) =>
((YdbParameter)parameter).YdbDbType = YdbDbType.List | ydbDbType;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

namespace EntityFrameworkCore.Ydb.Storage.Internal.Mapping;

public class YdbTextTypeMapping : RelationalTypeMapping
public sealed class YdbTextTypeMapping : RelationalTypeMapping
{
public static YdbTextTypeMapping Default { get; } = new("Text");

public YdbTextTypeMapping(string storeType)
private YdbTextTypeMapping(string storeType)
: base(
new RelationalTypeMappingParameters(
new CoreTypeMappingParameters(
Expand All @@ -23,15 +23,15 @@ public YdbTextTypeMapping(string storeType)
{
}

protected YdbTextTypeMapping(RelationalTypeMappingParameters parameters)
private YdbTextTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters)
{
}

protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new YdbTextTypeMapping(parameters);

protected virtual string EscapeSqlLiteral(string literal) => literal.Replace("'", "\\'");
private static string EscapeSqlLiteral(string literal) => literal.Replace("'", "\\'");

protected override string GenerateNonNullSqlLiteral(object value) => $"'{EscapeSqlLiteral((string)value)}'u";
}
53 changes: 46 additions & 7 deletions src/EFCore.Ydb/src/Storage/Internal/YdbTypeMappingSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text.Json;
using EntityFrameworkCore.Ydb.Storage.Internal.Mapping;
using Microsoft.EntityFrameworkCore.Storage;
using Ydb.Sdk.Ado.YdbType;
using Type = System.Type;

namespace EntityFrameworkCore.Ydb.Storage.Internal;
Expand Down Expand Up @@ -125,19 +127,56 @@ RelationalTypeMappingSourceDependencies relationalDependencies
var clrType = mappingInfo.ClrType;
var storeTypeName = mappingInfo.StoreTypeName;

if (storeTypeName is null || !StoreTypeMapping.TryGetValue(storeTypeName, out var mappings))
if (storeTypeName is not null && StoreTypeMapping.TryGetValue(storeTypeName, out var mappings))
{
return clrType is null ? null : ClrTypeMapping.GetValueOrDefault(clrType);
}
// We found the user-specified store type. No CLR type was provided - we're probably
// scaffolding from an existing database, take the first mapping as the default.
if (clrType is null)
{
return mappings[0];
}

foreach (var m in mappings)
{
if (m.ClrType == clrType)
// A CLR type was provided - look for a mapping between the store and CLR types. If not found, fail
// immediately.
foreach (var m in mappings)
{
return m;
if (m.ClrType == clrType)
{
return m;
}
}
}

return clrType is null ? null : ClrTypeMapping.GetValueOrDefault(clrType);
}

protected override RelationalTypeMapping? FindCollectionMapping(
RelationalTypeMappingInfo info,
Type modelType,
Type? providerType,
CoreTypeMapping? elementMapping
)
{
var elementType = modelType.IsArray
? modelType.GetElementType()
: modelType.GetInterfaces()
.FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>))?
.GetGenericArguments()[0];

if (elementType == null)
return null;

elementType = Nullable.GetUnderlyingType(elementType) ?? elementType;

var elementTypeMapping = FindMapping(elementType);

if (elementTypeMapping == null)
return null;

var ydbDbType = elementTypeMapping is IYdbTypeMapping ydbTypeMapping
? ydbTypeMapping.YdbDbType
: (elementTypeMapping.DbType ?? DbType.Object).ToYdbDbType();

return new YdbListTypeMapping(ydbDbType, elementTypeMapping.StoreType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@
using Microsoft.EntityFrameworkCore.Storage;
using Xunit;

namespace EntityFrameworkCore.Ydb.FunctionalTests.Query;
namespace EntityFrameworkCore.Ydb.FunctionalTests;

public class DecimalParameterizedYdbTest
{
private static DbContextOptions<ParametricDecimalContext> BuildOptions() =>
new DbContextOptionsBuilder<ParametricDecimalContext>()
.UseYdb("Host=localhost;Port=2136")
.EnableServiceProviderCaching(false)
.LogTo(Console.WriteLine)
.Options;

public static TheoryData<int, int, decimal> SuccessCases => new()
Expand Down Expand Up @@ -52,23 +51,17 @@ public async Task Should_RoundtripDecimal_When_ValueFitsPrecisionAndScale(int p,
await using var ctx = NewCtx(p, s);
await testStore.CleanAsync(ctx);
await ctx.Database.EnsureCreatedAsync();
try
{
var e = new ParamItem { Price = value };
ctx.Add(e);
await ctx.SaveChangesAsync();
var got = await ctx.Items.SingleAsync(x => x.Id == e.Id);
Assert.Equal(value, got.Price);
var tms = ctx.GetService<IRelationalTypeMappingSource>();
var et = ctx.Model.FindEntityType(typeof(ParamItem))!;
var prop = et.FindProperty(nameof(ParamItem.Price))!;
var mapping = tms.FindMapping(prop)!;
Assert.Equal($"Decimal({p}, {s})", mapping.StoreType);
}
finally
{
await ctx.Database.EnsureDeletedAsync();
}

var e = new ParamItem { Price = value };
ctx.Add(e);
await ctx.SaveChangesAsync();
var got = await ctx.Items.SingleAsync(x => x.Id == e.Id);
Assert.Equal(value, got.Price);
var tms = ctx.GetService<IRelationalTypeMappingSource>();
var et = ctx.Model.FindEntityType(typeof(ParamItem))!;
var prop = et.FindProperty(nameof(ParamItem.Price))!;
var mapping = tms.FindMapping(prop)!;
Assert.Equal($"Decimal({p}, {s})", mapping.StoreType);
}

[Theory]
Expand All @@ -80,15 +73,9 @@ public async Task Should_ThrowOverflow_When_ValueExceedsPrecisionOrScale(int p,
await using var ctx = NewCtx(p, s);
await testStore.CleanAsync(ctx);
await ctx.Database.EnsureCreatedAsync();
try
{
ctx.Add(new ParamItem { Price = value });
await Assert.ThrowsAsync<DbUpdateException>(() => ctx.SaveChangesAsync());
}
finally
{
await ctx.Database.EnsureDeletedAsync();
}

ctx.Add(new ParamItem { Price = value });
await Assert.ThrowsAsync<DbUpdateException>(() => ctx.SaveChangesAsync());
}

[Theory]
Expand All @@ -101,25 +88,19 @@ public async Task Should_SumDecimal_When_ValueFitsPrecisionAndScale(int p, int s
await using var ctx = NewCtx(p, s);
await testStore.CleanAsync(ctx);
await ctx.Database.EnsureCreatedAsync();
try
{
for (var i = 0; i < multiplier; i++)
ctx.Add(new ParamItem { Price = value });
await ctx.SaveChangesAsync();
var got = await ctx.Items.Select(x => x.Price).SumAsync();

Assert.Equal(value * multiplier, got);

var tms = ctx.GetService<IRelationalTypeMappingSource>();
var et = ctx.Model.FindEntityType(typeof(ParamItem))!;
var prop = et.FindProperty(nameof(ParamItem.Price))!;
var mapping = tms.FindMapping(prop)!;
Assert.Equal($"Decimal({p}, {s})", mapping.StoreType);
}
finally
{
await ctx.Database.EnsureDeletedAsync();
}

for (var i = 0; i < multiplier; i++)
ctx.Add(new ParamItem { Price = value });
await ctx.SaveChangesAsync();
var got = await ctx.Items.Select(x => x.Price).SumAsync();

Assert.Equal(value * multiplier, got);

var tms = ctx.GetService<IRelationalTypeMappingSource>();
var et = ctx.Model.FindEntityType(typeof(ParamItem))!;
var prop = et.FindProperty(nameof(ParamItem.Price))!;
var mapping = tms.FindMapping(prop)!;
Assert.Equal($"Decimal({p}, {s})", mapping.StoreType);
}

public sealed class ParametricDecimalContext(DbContextOptions<ParametricDecimalContext> options, int p, int s)
Expand Down
Loading
Loading