Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
25 changes: 25 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,31 @@ jobs:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Run Topic tests
run: dotnet test src/Ydb.Sdk/tests/Tests.csproj --filter "FullyQualifiedName~Topic" -f ${{ matrix.dotnet-target-framework }} -l "console;verbosity=detailed"
efcore-functional-tests:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
ydb-version: [ 'latest', 'trunk' ]
services:
ydb:
image: ydbplatform/local-ydb:${{ matrix.ydb-version }}
ports:
- 2135:2135
- 2136:2136
- 8765:8765
env:
YDB_LOCAL_SURVIVE_RESTART: true
options: '--name ydb-local -h localhost'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
- name: Run EfCore tests
run: dotnet test src/EfCore.Ydb/test/EfCore.Ydb.FunctionalTests/EfCore.Ydb.FunctionalTests.csproj -l "console;verbosity=detailed"
integration-tests:
runs-on: ubuntu-22.04
strategy:
Expand Down
28 changes: 13 additions & 15 deletions src/EfCore.Ydb/src/Migrations/YdbMigrationsSqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

namespace EfCore.Ydb.Migrations;

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

builder.Append("CREATE ");
// TODO: Support EXTERNAL tables?
builder
.Append("TABLE ")
builder.Append("CREATE TABLE ")
.Append(DelimitIdentifier(operation.Name, operation.Schema))
.AppendLine(" (");

Expand Down Expand Up @@ -66,9 +62,10 @@ MigrationCommandListBuilder builder
{
columnType = columnType.ToLower() switch
{
"int8" or "int16" => "SmallSerial",
"int32" => "Serial",
"int64" => "Bigserial",
_ => throw new NotSupportedException("Serial column supported only for int32 and int64")
_ => throw new NotSupportedException($"Serial column isn't supported for {columnType} type")
};
}

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

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

if (operation.NewName is null || operation.NewName == operation.Name)
Expand All @@ -120,22 +117,23 @@ protected override void Generate(RenameTableOperation operation, IModel? model,
builder
.Append("ALTER TABLE ")
.Append(DelimitIdentifier(operation.Name, operation.Schema))
.AppendLine("RENAME TO")
.Append(DelimitIdentifier(operation.NewName, operation.Schema));
.Append(" RENAME TO ")
.Append(DelimitIdentifier(operation.NewName, operation.Schema))
.AppendLine(";");
EndStatement(builder);
}

protected override void Generate(
InsertDataOperation operation, IModel? model, MigrationCommandListBuilder builder, bool terminate = true
InsertDataOperation operation,
IModel? model,
MigrationCommandListBuilder builder,
bool terminate = true
)
{
var sqlBuilder = new StringBuilder();
foreach (var modificationCommand in GenerateModificationCommands(operation, model))
{
SqlGenerator.AppendInsertOperation(
sqlBuilder,
modificationCommand,
0);
SqlGenerator.AppendInsertOperation(sqlBuilder, modificationCommand, 0);
}

builder.Append(sqlBuilder.ToString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public class YdbBoolTypeMapping : BoolTypeMapping
{
public new static YdbBoolTypeMapping Default { get; } = new();

private YdbBoolTypeMapping() : base("BOOL")
private YdbBoolTypeMapping() : base("Bool")
{
}

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

protected override string GenerateNonNullSqlLiteral(object value)
=> (bool)value ? "true" : "false";
=> (bool)value ? "TRUE" : "FALSE";
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using System;
using System.Data.Common;
using System.Reflection;
using Microsoft.EntityFrameworkCore.Storage;

namespace EfCore.Ydb.Storage.Internal.Mapping;

public class YdbDecimalTypeMapping : RelationalTypeMapping
public class YdbDecimalTypeMapping : DecimalTypeMapping
{
private const byte DefaultPrecision = 22;
private const byte DefaultScale = 9;

public YdbDecimalTypeMapping(Type? type) : this(
new RelationalTypeMappingParameters(
new CoreTypeMappingParameters(type ?? typeof(decimal)),
Expand All @@ -26,14 +27,5 @@ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters p

protected override string ProcessStoreType(
RelationalTypeMappingParameters parameters, string storeType, string storeTypeNameBase
) => storeType == "BigInteger" && parameters.Precision != null
? $"Decimal({parameters.Precision}, 0)"
: parameters.Precision is null
? storeType
: parameters.Scale is null
? $"Decimal({parameters.Precision}, 0)"
: $"Decimal({parameters.Precision}, {parameters.Scale})";

public override MethodInfo GetDataReaderMethod() =>
typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.GetDecimal), [typeof(int)]) ?? throw new Exception();
) => $"{storeType}({parameters.Precision ?? DefaultPrecision}, {parameters.Scale ?? DefaultScale})";
}
20 changes: 20 additions & 0 deletions src/EfCore.Ydb/src/Storage/Internal/YdbSqlGenerationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,24 @@ public override void DelimitIdentifier(StringBuilder builder, string identifier)
builder.Append('`').Append(identifier).Append('`');

public override string DelimitIdentifier(string identifier) => $"`{identifier}`";

public override string EscapeIdentifier(string identifier) => identifier.Replace("`", "``");

public override void EscapeIdentifier(StringBuilder builder, string identifier)
{
var length = builder.Length;
builder.Append(identifier);
builder.Replace("`", "``", length, identifier.Length);
}

public override string DelimitIdentifier(string name, string? schema) =>
DelimitIdentifier(
(!string.IsNullOrEmpty(schema) ? schema + "/" : string.Empty)
+ name
);

public override void DelimitIdentifier(StringBuilder builder, string name, string? schema) =>
builder.Append(
DelimitIdentifier((!string.IsNullOrEmpty(schema) ? schema + "/" : string.Empty) + name)
);
}
157 changes: 68 additions & 89 deletions src/EfCore.Ydb/src/Storage/Internal/YdbTypeMappingSource.cs
Original file line number Diff line number Diff line change
@@ -1,135 +1,114 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Numerics;
using System.Text.Json;
using EfCore.Ydb.Storage.Internal.Mapping;
using Microsoft.EntityFrameworkCore.Storage;
using Type = System.Type;

namespace EfCore.Ydb.Storage.Internal;

public sealed class YdbTypeMappingSource : RelationalTypeMappingSource
public sealed class YdbTypeMappingSource(
TypeMappingSourceDependencies dependencies,
RelationalTypeMappingSourceDependencies relationalDependencies
) : RelationalTypeMappingSource(dependencies, relationalDependencies)
{
private ConcurrentDictionary<string, RelationalTypeMapping[]> StoreTypeMapping { get; }
private ConcurrentDictionary<Type, RelationalTypeMapping> ClrTypeMapping { get; }

#region Mappings

private readonly YdbBoolTypeMapping _bool = YdbBoolTypeMapping.Default;
private static readonly YdbBoolTypeMapping Bool = YdbBoolTypeMapping.Default;

private static readonly SByteTypeMapping Int8 = new("Int8", DbType.SByte);
private static readonly ShortTypeMapping Int16 = new("Int16", DbType.Int16);
private static readonly IntTypeMapping Int32 = new("Int32", DbType.Int32);
private static readonly LongTypeMapping Int64 = new("Int64", DbType.Int64);

private readonly SByteTypeMapping _int8 = new("Int8", DbType.SByte);
private readonly ShortTypeMapping _int16 = new("Int16", DbType.Int16);
private readonly IntTypeMapping _int32 = new("Int32", DbType.Int32);
private readonly LongTypeMapping _int64 = new("Int64", DbType.Int64);
private static readonly ByteTypeMapping Uint8 = new("Uint8", DbType.Byte);
private static readonly UShortTypeMapping Uint16 = new("Uint16", DbType.UInt16);
private static readonly UIntTypeMapping Uint32 = new("Uint32", DbType.UInt32);
private static readonly ULongTypeMapping Uint64 = new("Uint64", DbType.UInt64);

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

private readonly FloatTypeMapping _float = new("Float", DbType.Single);
private readonly DoubleTypeMapping _double = new("Double", DbType.Double);
private readonly YdbDecimalTypeMapping _biginteger = new(typeof(BigInteger));
private readonly YdbDecimalTypeMapping _decimal = new(typeof(decimal));
private readonly YdbDecimalTypeMapping _decimalAsDouble = new(typeof(double));
private readonly YdbDecimalTypeMapping _decimalAsFloat = new(typeof(float));
private static readonly YdbDecimalTypeMapping Decimal = new(typeof(decimal));

private readonly StringTypeMapping _text = new("Text", DbType.String);
private readonly YdbStringTypeMapping _ydbString = YdbStringTypeMapping.Default;
private readonly YdbBytesTypeMapping _bytes = YdbBytesTypeMapping.Default;
private readonly YdbJsonTypeMapping _json = new("Json", typeof(JsonElement), DbType.String);
private static readonly StringTypeMapping Text = new("Text", DbType.String);
private static readonly YdbBytesTypeMapping Bytes = YdbBytesTypeMapping.Default;
private static readonly YdbJsonTypeMapping Json = new("Json", typeof(JsonElement), DbType.String);

private readonly DateOnlyTypeMapping _date = new("Date");
private readonly DateTimeTypeMapping _dateTime = new("Datetime");
private readonly DateTimeTypeMapping _timestamp = new("Timestamp");
private readonly TimeSpanTypeMapping _interval = new("Interval");
private static readonly DateOnlyTypeMapping Date = new("Date");
private static readonly DateTimeTypeMapping DateTime = new("Datetime");
private static readonly DateTimeTypeMapping Timestamp = new("Timestamp");
private static readonly TimeSpanTypeMapping Interval = new("Interval");

#endregion

public YdbTypeMappingSource(
TypeMappingSourceDependencies dependencies,
RelationalTypeMappingSourceDependencies relationalDependencies
) : base(dependencies, relationalDependencies)
{
var storeTypeMappings = new Dictionary<string, RelationalTypeMapping[]>(StringComparer.OrdinalIgnoreCase)
private static readonly Dictionary<string, RelationalTypeMapping[]> StoreTypeMapping =
new(StringComparer.OrdinalIgnoreCase)
{
{ "Bool", [_bool] },
{ "Bool", [Bool] },

{ "Int8", [_int8] },
{ "Int16", [_int16] },
{ "Int32", [_int32] },
{ "Int64", [_int64] },
{ "Int8", [Int8] },
{ "Int16", [Int16] },
{ "Int32", [Int32] },
{ "Int64", [Int64] },

{ "Uint8", [_uint8] },
{ "Uint16", [_uint16] },
{ "Uint32", [_uint32] },
{ "Uint64", [_uint64] },
{ "Uint8", [Uint8] },
{ "Uint16", [Uint16] },
{ "Uint32", [Uint32] },
{ "Uint64", [Uint64] },

{ "Float", [_float] },
{ "Double", [_double] },
{ "Float", [Float] },
{ "Double", [Double] },

{ "Decimal", [_decimal, _decimalAsDouble, _decimalAsFloat, _biginteger] },
{ "Date", [Date] },
{ "DateTime", [DateTime] },
{ "Timestamp", [Timestamp] },
{ "Interval", [Interval] },

{ "Text", [_text] },
{ "String", [_ydbString] },
{ "Json", [_json] },
{ "Text", [Text] },
{ "Bytes", [Bytes] },

{ "Bytes", [_bytes] },
{ "Decimal", [Decimal] },

{ "Date", [_date] },
{ "DateTime", [_dateTime] },
{ "Timestamp", [_timestamp] },
{ "Interval", [_interval] }
{ "Json", [Json] }
};
var clrTypeMappings = new Dictionary<Type, RelationalTypeMapping>
{
{ typeof(bool), _bool },

{ typeof(sbyte), _int8 },
{ typeof(short), _int16 },
{ typeof(int), _int32 },
{ typeof(long), _int64 },
private static readonly Dictionary<Type, RelationalTypeMapping> ClrTypeMapping = new()
{
{ typeof(bool), Bool },

{ typeof(byte), _uint8 },
{ typeof(ushort), _uint16 },
{ typeof(uint), _uint32 },
{ typeof(ulong), _uint64 },
{ typeof(sbyte), Int8 },
{ typeof(short), Int16 },
{ typeof(int), Int32 },
{ typeof(long), Int64 },

{ typeof(float), _float },
{ typeof(double), _double },
{ typeof(decimal), _decimal },
{ typeof(byte), Uint8 },
{ typeof(ushort), Uint16 },
{ typeof(uint), Uint32 },
{ typeof(ulong), Uint64 },

{ typeof(string), _text },
{ typeof(byte[]), _bytes },
{ typeof(JsonElement), _json },
{ typeof(float), Float },
{ typeof(double), Double },
{ typeof(decimal), Decimal },

{ typeof(DateOnly), _date },
{ typeof(DateTime), _timestamp },
{ typeof(TimeSpan), _interval }
};
{ typeof(string), Text },
{ typeof(byte[]), Bytes },
{ typeof(JsonElement), Json },

StoreTypeMapping = new ConcurrentDictionary<string, RelationalTypeMapping[]>(storeTypeMappings);
ClrTypeMapping = new ConcurrentDictionary<Type, RelationalTypeMapping>(clrTypeMappings);
}
{ typeof(DateOnly), Date },
{ typeof(DateTime), Timestamp },
{ typeof(TimeSpan), Interval }
};

protected override RelationalTypeMapping? FindMapping(in RelationalTypeMappingInfo mappingInfo)
=> FindBaseMapping(mappingInfo)
?? base.FindMapping(mappingInfo);
=> base.FindMapping(mappingInfo) ?? FindBaseMapping(mappingInfo)?.Clone(mappingInfo);

private RelationalTypeMapping? FindBaseMapping(in RelationalTypeMappingInfo mappingInfo)
private static RelationalTypeMapping? FindBaseMapping(in RelationalTypeMappingInfo mappingInfo)
{
var clrType = mappingInfo.ClrType;
var storeTypeName = mappingInfo.StoreTypeName;

// Special case.
// If property has [YdbString] attribute then we use STRING type instead of TEXT
if (mappingInfo.StoreTypeName == "string")
{
return _ydbString;
}

if (storeTypeName is null)
{
return clrType is null ? null : ClrTypeMapping.GetValueOrDefault(clrType);
Expand Down
Loading
Loading