forked from npgsql/efcore.pg
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes npgsql#212
- Loading branch information
Showing
14 changed files
with
608 additions
and
3 deletions.
There are no files selected for viewing
139 changes: 139 additions & 0 deletions
139
src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlHstoreTranslator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
using System.Collections.Immutable; | ||
using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions; | ||
using Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; | ||
using static Npgsql.EntityFrameworkCore.PostgreSQL.Utilities.Statics; | ||
|
||
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal; | ||
|
||
/// <summary> | ||
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to | ||
/// the same compatibility standards as public APIs. It may be changed or removed without notice in | ||
/// any release. You should only use it directly in your code with extreme caution and knowing that | ||
/// doing so can result in application failures when updating to a new Entity Framework Core release. | ||
/// </summary> | ||
public class NpgsqlHstoreTranslator : IMethodCallTranslator, IMemberTranslator | ||
{ | ||
private static readonly Type DictionaryType = typeof(Dictionary<string, string>); | ||
private static readonly Type ImmutableDictionaryType = typeof(ImmutableDictionary<string, string>); | ||
|
||
private static readonly MethodInfo Dictionary_ContainsKey = | ||
DictionaryType.GetMethod(nameof(Dictionary<string, string>.ContainsKey))!; | ||
|
||
private static readonly MethodInfo ImmutableDictionary_ContainsKey = | ||
ImmutableDictionaryType.GetMethod(nameof(ImmutableDictionary<string, string>.ContainsKey))!; | ||
|
||
private static readonly MethodInfo Dictionary_ContainsValue = | ||
DictionaryType.GetMethod(nameof(Dictionary<string, string>.ContainsValue))!; | ||
|
||
private static readonly MethodInfo ImmutableDictionary_ContainsValue = | ||
ImmutableDictionaryType.GetMethod(nameof(ImmutableDictionary<string, string>.ContainsValue))!; | ||
|
||
private static readonly MethodInfo Dictionary_Item_Getter = | ||
DictionaryType.GetProperty("Item")!.GetMethod!; | ||
|
||
private static readonly MethodInfo ImmutableDictionary_Item_Getter = | ||
ImmutableDictionaryType.GetProperty("Item")!.GetMethod!; | ||
|
||
private static readonly PropertyInfo Dictionary_Count = DictionaryType.GetProperty(nameof(Dictionary<string, string>.Count))!; | ||
|
||
private static readonly PropertyInfo ImmutableDictionary_Count = | ||
ImmutableDictionaryType.GetProperty(nameof(ImmutableDictionary<string, string>.Count))!; | ||
|
||
private static readonly PropertyInfo ImmutableDictionary_IsEmpty = | ||
ImmutableDictionaryType.GetProperty(nameof(ImmutableDictionary<string, string>.IsEmpty))!; | ||
|
||
private readonly RelationalTypeMapping _stringListTypeMapping; | ||
private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory; | ||
|
||
/// <summary> | ||
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to | ||
/// the same compatibility standards as public APIs. It may be changed or removed without notice in | ||
/// any release. You should only use it directly in your code with extreme caution and knowing that | ||
/// doing so can result in application failures when updating to a new Entity Framework Core release. | ||
/// </summary> | ||
public NpgsqlHstoreTranslator(IRelationalTypeMappingSource typeMappingSource, NpgsqlSqlExpressionFactory sqlExpressionFactory) | ||
{ | ||
_sqlExpressionFactory = sqlExpressionFactory; | ||
_stringListTypeMapping = typeMappingSource.FindMapping(typeof(List<string>))!; | ||
} | ||
|
||
/// <summary> | ||
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to | ||
/// the same compatibility standards as public APIs. It may be changed or removed without notice in | ||
/// any release. You should only use it directly in your code with extreme caution and knowing that | ||
/// doing so can result in application failures when updating to a new Entity Framework Core release. | ||
/// </summary> | ||
public SqlExpression? Translate( | ||
SqlExpression? instance, | ||
MethodInfo method, | ||
IReadOnlyList<SqlExpression> arguments, | ||
IDiagnosticsLogger<DbLoggerCategory.Query> logger) | ||
{ | ||
if (instance?.TypeMapping is null || instance.TypeMapping.StoreType != NpgsqlHstoreTypeMapping.HstoreType) | ||
{ | ||
return null; | ||
} | ||
|
||
if (method == Dictionary_ContainsKey || method == ImmutableDictionary_ContainsKey) | ||
{ | ||
return _sqlExpressionFactory.MakePostgresBinary(PgExpressionType.HStoreContainsKey, instance, arguments[0]); | ||
} | ||
|
||
if (method == Dictionary_ContainsValue || method == ImmutableDictionary_ContainsValue) | ||
{ | ||
return _sqlExpressionFactory.Equal( | ||
arguments[0], | ||
_sqlExpressionFactory.Function( | ||
"ANY", new[] | ||
{ | ||
_sqlExpressionFactory.Function( | ||
"avals", new[] { instance }, false, FalseArrays[1], typeof(List<string>), _stringListTypeMapping) | ||
}, false, FalseArrays[1], typeof(string))); | ||
} | ||
|
||
if (method == Dictionary_Item_Getter || method == ImmutableDictionary_Item_Getter) | ||
{ | ||
return _sqlExpressionFactory.MakePostgresBinary(PgExpressionType.HStoreValueForKey, instance, arguments[0]); | ||
} | ||
return null; | ||
} | ||
|
||
/// <summary> | ||
/// Translates a LINQ <see cref="T:System.Linq.Expressions.MemberExpression" /> to a SQL equivalent. | ||
/// </summary> | ||
/// <param name="instance">A SQL representation of <see cref="P:System.Linq.Expressions.MemberExpression.Expression" />.</param> | ||
/// <param name="member">The member info from <see cref="P:System.Linq.Expressions.MemberExpression.Member" />.</param> | ||
/// <param name="returnType">The return type from <see cref="P:System.Linq.Expressions.Expression.Type" />.</param> | ||
/// <param name="logger">The query logger to use.</param> | ||
/// <returns>A SQL translation of the <see cref="T:System.Linq.Expressions.MemberExpression" />.</returns> | ||
public SqlExpression? Translate( | ||
SqlExpression? instance, | ||
MemberInfo member, | ||
Type returnType, | ||
IDiagnosticsLogger<DbLoggerCategory.Query> logger) | ||
{ | ||
|
||
if (instance?.TypeMapping is null || instance.TypeMapping.StoreType != NpgsqlHstoreTypeMapping.HstoreType) | ||
{ | ||
return null; | ||
} | ||
|
||
if (member == Dictionary_Count || member == ImmutableDictionary_Count) | ||
{ | ||
return _sqlExpressionFactory.Function("array_length", new [] | ||
{ | ||
_sqlExpressionFactory.Function( | ||
"akeys", new[] { instance }, false, FalseArrays[1], typeof(List<string>), _stringListTypeMapping), | ||
_sqlExpressionFactory.Constant(1) | ||
}, false, FalseArrays[2], typeof(int)); | ||
} | ||
|
||
if (member == ImmutableDictionary_IsEmpty) | ||
{ | ||
return _sqlExpressionFactory.Equal( | ||
Translate(instance, Dictionary_Count, typeof(int), logger)!, | ||
_sqlExpressionFactory.Constant(0)); | ||
} | ||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
73 changes: 73 additions & 0 deletions
73
test/EFCore.PG.FunctionalTests/Query/HstoreQueryFixture.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
using Npgsql.EntityFrameworkCore.PostgreSQL.TestModels.Dictionary; | ||
using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; | ||
|
||
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; | ||
|
||
public class HstoreQueryFixture : SharedStoreFixtureBase<DictionaryQueryContext>, IQueryFixtureBase, ITestSqlLoggerFactory | ||
{ | ||
protected override string StoreName | ||
=> "HstoreQueryTest"; | ||
|
||
protected override ITestStoreFactory TestStoreFactory | ||
=> NpgsqlTestStoreFactory.Instance; | ||
|
||
public TestSqlLoggerFactory TestSqlLoggerFactory | ||
=> (TestSqlLoggerFactory)ListLoggerFactory; | ||
|
||
private DictionaryQueryData _expectedData; | ||
|
||
public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) | ||
=> base.AddOptions(builder).ConfigureWarnings(wcb => wcb.Ignore(CoreEventId.CollectionWithoutComparer)); | ||
|
||
protected override Task SeedAsync(DictionaryQueryContext context) | ||
=> DictionaryQueryContext.SeedAsync(context); | ||
|
||
public Func<DbContext> GetContextCreator() | ||
=> CreateContext; | ||
|
||
public ISetSource GetExpectedData() | ||
=> _expectedData ??= new DictionaryQueryData(); | ||
|
||
public IReadOnlyDictionary<Type, object> EntitySorters | ||
=> new Dictionary<Type, Func<object, object>> | ||
{ | ||
{ typeof(DictionaryEntity), e => ((DictionaryEntity)e)?.Id }, { typeof(DictionaryContainerEntity), e => ((DictionaryContainerEntity)e)?.Id } | ||
}.ToDictionary(e => e.Key, e => (object)e.Value); | ||
|
||
public IReadOnlyDictionary<Type, object> EntityAsserters | ||
=> new Dictionary<Type, Action<object, object>> | ||
{ | ||
{ | ||
typeof(DictionaryEntity), (e, a) => | ||
{ | ||
Assert.Equal(e is null, a is null); | ||
if (a is not null) | ||
{ | ||
var ee = (DictionaryEntity)e; | ||
var aa = (DictionaryEntity)a; | ||
|
||
Assert.Equal(ee.Id, aa.Id); | ||
Assert.Equal(ee.Dictionary, ee.Dictionary); | ||
Assert.Equal(ee.ImmutableDictionary, ee.ImmutableDictionary); | ||
Assert.Equal(ee.NullableDictionary, ee.NullableDictionary); | ||
Assert.Equal(ee.NullableImmutableDictionary, ee.NullableImmutableDictionary); | ||
|
||
} | ||
} | ||
}, | ||
{ | ||
typeof(DictionaryContainerEntity), (e, a) => | ||
{ | ||
Assert.Equal(e is null, a is null); | ||
if (a is not null) | ||
{ | ||
var ee = (DictionaryContainerEntity)e; | ||
var aa = (DictionaryContainerEntity)a; | ||
|
||
Assert.Equal(ee.Id, aa.Id); | ||
Assert.Equal(ee.DictionaryEntities, ee.DictionaryEntities); | ||
} | ||
} | ||
} | ||
}.ToDictionary(e => e.Key, e => (object)e.Value); | ||
} |
Oops, something went wrong.