Skip to content

Commit 784cb49

Browse files
committed
More types. Added basic json, decimal, string (alternative for TEXT), bytes (blobs), date/time
1 parent 769c5fa commit 784cb49

File tree

11 files changed

+361
-11
lines changed

11 files changed

+361
-11
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using System;
2+
3+
namespace EfCore.Ydb.Abstractions;
4+
5+
[AttributeUsage(AttributeTargets.Property)]
6+
public class YdbStringAttribute: Attribute
7+
{
8+
}

src/EfCore.Ydb/src/Metadata/Conventions/YdbConventionSetBuilder.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
12
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
23

34
namespace EfCore.Ydb.Metadata.Conventions;
@@ -11,4 +12,11 @@ RelationalConventionSetBuilderDependencies relationalDependencies
1112
) : base(dependencies, relationalDependencies)
1213
{
1314
}
15+
16+
public override ConventionSet CreateConventionSet()
17+
{
18+
var coreConventions = base.CreateConventionSet();
19+
coreConventions.Add(new YdbStringAttributeConvention(Dependencies));
20+
return coreConventions;
21+
}
1422
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System.Reflection;
2+
using EfCore.Ydb.Abstractions;
3+
using Microsoft.EntityFrameworkCore;
4+
using Microsoft.EntityFrameworkCore.Metadata.Builders;
5+
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
6+
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
7+
8+
namespace EfCore.Ydb.Metadata.Conventions;
9+
10+
public class YdbStringAttributeConvention
11+
: PropertyAttributeConventionBase<YdbStringAttribute>
12+
{
13+
public YdbStringAttributeConvention(ProviderConventionSetBuilderDependencies dependencies) : base(dependencies)
14+
{
15+
}
16+
17+
protected override void ProcessPropertyAdded(
18+
IConventionPropertyBuilder propertyBuilder,
19+
YdbStringAttribute attribute,
20+
MemberInfo clrMember,
21+
IConventionContext context
22+
)
23+
{
24+
propertyBuilder.HasColumnType("string");
25+
}
26+
}

src/EfCore.Ydb/src/Query/Internal/YdbAggregateMethodCallTranslatorProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ RelationalAggregateMethodCallTranslatorProviderDependencies dependencies
1414

1515
AddTranslators(
1616
[
17-
new YdbQueryableAggregateMethodTranslator(sqlExpressionFactory)
17+
new YdbQueryableAggregateMethodTranslator(sqlExpressionFactory, dependencies.RelationalTypeMappingSource)
1818
]);
1919
}
2020
}

src/EfCore.Ydb/src/Query/Internal/YdbQuerySqlGenerator.cs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System.Collections.Generic;
2+
using System.Diagnostics;
3+
using System.Diagnostics.CodeAnalysis;
24
using System.Linq;
35
using System.Linq.Expressions;
46
using Microsoft.EntityFrameworkCore.Query;
@@ -10,11 +12,22 @@ namespace EfCore.Ydb.Query.Internal;
1012
public class YdbQuerySqlGenerator : QuerySqlGenerator
1113
{
1214
protected readonly ISqlGenerationHelper SqlGenerationHelper;
15+
protected readonly IRelationalTypeMappingSource TypeMappingSource;
1316
protected bool SkipAliases;
1417

15-
public YdbQuerySqlGenerator(QuerySqlGeneratorDependencies dependencies) : base(dependencies)
18+
public YdbQuerySqlGenerator(
19+
QuerySqlGeneratorDependencies dependencies,
20+
IRelationalTypeMappingSource typeMappingSource
21+
) : base(dependencies)
1622
{
1723
SqlGenerationHelper = dependencies.SqlGenerationHelper;
24+
TypeMappingSource = typeMappingSource;
25+
}
26+
27+
[return: NotNullIfNotNull("node")]
28+
public override Expression? Visit(Expression? node)
29+
{
30+
return base.Visit(node);
1831
}
1932

2033
protected override Expression VisitColumn(ColumnExpression columnExpression)
@@ -225,4 +238,49 @@ TableExpressionBase fromTable
225238
&& select.Tables[0].Equals(fromTable)
226239
);
227240
}
241+
242+
protected override string GetOperator(SqlBinaryExpression binaryExpression)
243+
=> binaryExpression.OperatorType == ExpressionType.Add
244+
&& binaryExpression.Type == typeof(string)
245+
? " || "
246+
: base.GetOperator(binaryExpression);
247+
248+
protected override Expression VisitJsonScalar(JsonScalarExpression jsonScalarExpression)
249+
{
250+
Sql.Append("JSON_VALUE(");
251+
Visit(jsonScalarExpression.Json);
252+
Sql.Append(",");
253+
254+
var path = jsonScalarExpression.Path;
255+
if (!path.Any())
256+
{
257+
return jsonScalarExpression;
258+
}
259+
260+
Sql.Append("\"$.");
261+
for (var i = 0; i < path.Count; i++)
262+
{
263+
var pathSegment = path[i];
264+
var isFirst = i == 0;
265+
266+
switch (pathSegment)
267+
{
268+
case { PropertyName: string propertyName }:
269+
Sql
270+
.Append(isFirst ? "" : ".")
271+
.Append(Dependencies.SqlGenerationHelper.DelimitJsonPathElement(propertyName));
272+
break;
273+
case { ArrayIndex: SqlConstantExpression arrayIndex }:
274+
Sql.Append("[");
275+
Visit(pathSegment.ArrayIndex);
276+
Sql.Append("]");
277+
break;
278+
default:
279+
throw new UnreachableException();
280+
}
281+
}
282+
283+
Sql.Append("\")");
284+
return jsonScalarExpression;
285+
}
228286
}
Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
using Microsoft.EntityFrameworkCore.Query;
2+
using Microsoft.EntityFrameworkCore.Storage;
23

34
namespace EfCore.Ydb.Query.Internal;
45

56
public class YdbQuerySqlGeneratorFactory : IQuerySqlGeneratorFactory
67
{
78
private readonly QuerySqlGeneratorDependencies _dependencies;
9+
private readonly IRelationalTypeMappingSource _typeMappingSource;
810

911
public YdbQuerySqlGeneratorFactory(
10-
QuerySqlGeneratorDependencies dependencies
11-
) => _dependencies = dependencies;
12+
QuerySqlGeneratorDependencies dependencies,
13+
IRelationalTypeMappingSource typeMappingSource
14+
)
15+
{
16+
_dependencies = dependencies;
17+
_typeMappingSource = typeMappingSource;
18+
}
1219

1320
public QuerySqlGenerator Create()
14-
=> new YdbQuerySqlGenerator(_dependencies);
21+
=> new YdbQuerySqlGenerator(_dependencies, _typeMappingSource);
1522
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System.Text;
2+
using Microsoft.EntityFrameworkCore.Storage;
3+
using Microsoft.EntityFrameworkCore.Storage.Json;
4+
5+
namespace EfCore.Ydb.Storage.Internal.Mapping;
6+
7+
public class YdbBytesTypeMapping : RelationalTypeMapping
8+
{
9+
public static YdbBytesTypeMapping Default { get; } = new();
10+
11+
public YdbBytesTypeMapping() : base(
12+
new RelationalTypeMappingParameters(
13+
new CoreTypeMappingParameters(
14+
typeof(byte[]),
15+
jsonValueReaderWriter: JsonByteArrayReaderWriter.Instance
16+
),
17+
storeType: "bytes",
18+
storeTypePostfix: StoreTypePostfix.None,
19+
dbType: System.Data.DbType.Binary,
20+
unicode: false
21+
)
22+
)
23+
{
24+
}
25+
26+
protected YdbBytesTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters)
27+
{
28+
}
29+
30+
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
31+
=> new YdbBytesTypeMapping(parameters);
32+
33+
protected override string GenerateNonNullSqlLiteral(object value)
34+
{
35+
var bytes = (byte[])value;
36+
return $"'{Encoding.UTF8.GetString(bytes)}'";
37+
}
38+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
using System.Data.Common;
3+
using System.Reflection;
4+
using Microsoft.EntityFrameworkCore.Storage;
5+
6+
namespace EfCore.Ydb.Storage.Internal.Mapping;
7+
8+
public class YdbDecimalTypeMapping : RelationalTypeMapping
9+
{
10+
public YdbDecimalTypeMapping(Type? type) : this(
11+
new RelationalTypeMappingParameters(
12+
new CoreTypeMappingParameters(type ?? typeof(decimal)),
13+
storeType: "decimal",
14+
dbType: System.Data.DbType.Decimal
15+
)
16+
)
17+
{
18+
}
19+
20+
protected YdbDecimalTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters)
21+
{
22+
}
23+
24+
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
25+
=> new YdbDecimalTypeMapping(parameters);
26+
27+
protected override string ProcessStoreType(
28+
RelationalTypeMappingParameters parameters, string storeType, string storeTypeNameBase
29+
)
30+
{
31+
if (storeType == "BigInteger" && parameters.Precision != null)
32+
{
33+
return $"DECIMAL({parameters.Precision}, 0)";
34+
}
35+
else
36+
{
37+
return parameters.Precision is null
38+
? storeType
39+
: parameters.Scale is null
40+
? $"DECIMAL({parameters.Precision}, 0)"
41+
: $"DECIMAL({parameters.Precision},{parameters.Scale})";
42+
}
43+
}
44+
45+
public override MethodInfo GetDataReaderMethod()
46+
{
47+
return typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.GetDecimal), [typeof(int)])!;
48+
}
49+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System;
2+
using System.Data;
3+
using System.Data.Common;
4+
using System.IO;
5+
using System.Linq.Expressions;
6+
using System.Reflection;
7+
using System.Text;
8+
using System.Text.Json;
9+
using Microsoft.EntityFrameworkCore.Storage;
10+
11+
namespace EfCore.Ydb.Storage.Internal.Mapping;
12+
13+
public class YdbJsonTypeMapping : JsonTypeMapping
14+
{
15+
public YdbJsonTypeMapping(string storeType, Type clrType, DbType? dbType) : base(storeType, clrType, dbType)
16+
{
17+
}
18+
19+
protected YdbJsonTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters)
20+
{
21+
}
22+
23+
private static readonly MethodInfo GetStringMethod
24+
= typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.GetString), [typeof(int)])!;
25+
26+
private static readonly PropertyInfo UTF8Property
27+
= typeof(Encoding).GetProperty(nameof(Encoding.UTF8))!;
28+
29+
private static readonly MethodInfo EncodingGetBytesMethod
30+
= typeof(Encoding).GetMethod(nameof(Encoding.GetBytes), [typeof(string)])!;
31+
32+
private static readonly ConstructorInfo MemoryStreamConstructor
33+
= typeof(MemoryStream).GetConstructor([typeof(byte[])])!;
34+
35+
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
36+
=> new YdbJsonTypeMapping(parameters);
37+
38+
public override MethodInfo GetDataReaderMethod()
39+
=> GetStringMethod;
40+
41+
protected override string GenerateNonNullSqlLiteral(object value)
42+
{
43+
switch (value)
44+
{
45+
case JsonDocument _:
46+
case JsonElement _:
47+
{
48+
using var stream = new MemoryStream();
49+
using var writer = new Utf8JsonWriter(stream);
50+
if (value is JsonDocument doc)
51+
{
52+
doc.WriteTo(writer);
53+
}
54+
else
55+
{
56+
((JsonElement)value).WriteTo(writer);
57+
}
58+
59+
writer.Flush();
60+
return $"'{Encoding.UTF8.GetString(stream.ToArray())}'";
61+
}
62+
case string s:
63+
return $"'{s}'";
64+
default:
65+
return $"'{JsonSerializer.Serialize(value)}'";
66+
}
67+
}
68+
69+
public override Expression CustomizeDataReaderExpression(Expression expression)
70+
=> Expression.New(
71+
MemoryStreamConstructor,
72+
Expression.Call(
73+
Expression.Property(null, UTF8Property),
74+
EncodingGetBytesMethod,
75+
expression));
76+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System.Text;
2+
using Microsoft.EntityFrameworkCore.Storage;
3+
using Microsoft.EntityFrameworkCore.Storage.Json;
4+
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
5+
6+
namespace EfCore.Ydb.Storage.Internal.Mapping;
7+
8+
public class YdbStringTypeMapping : RelationalTypeMapping
9+
{
10+
public static YdbStringTypeMapping Default { get; } = new();
11+
12+
public YdbStringTypeMapping() : base(
13+
new RelationalTypeMappingParameters(
14+
new CoreTypeMappingParameters(
15+
typeof(byte[]),
16+
new StringToBytesConverter(Encoding.UTF8),
17+
jsonValueReaderWriter: JsonByteArrayReaderWriter.Instance
18+
),
19+
storeType: "string",
20+
storeTypePostfix: StoreTypePostfix.None,
21+
dbType: System.Data.DbType.Binary,
22+
unicode: false
23+
)
24+
)
25+
{
26+
}
27+
28+
protected YdbStringTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters)
29+
{
30+
}
31+
32+
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
33+
=> new YdbStringTypeMapping(parameters);
34+
35+
protected override string GenerateNonNullSqlLiteral(object value)
36+
{
37+
var bytes = (byte[])value;
38+
return $"'{Encoding.UTF8.GetString(bytes)}'";
39+
}
40+
}

0 commit comments

Comments
 (0)