diff --git a/Directory.Packages.props b/Directory.Packages.props index 6ef512b87..e951f7b65 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,7 +1,7 @@ - 10.0.0-preview.5.25277.114 - 10.0.0-preview.5.25277.114 + 10.0.0-preview.6.25314.101 + 10.0.0-preview.6.25314.101 9.0.3 diff --git a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs index 896589719..b6cd534eb 100644 --- a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs +++ b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteMethodCallTranslatorPlugin.cs @@ -47,8 +47,6 @@ public NpgsqlNetTopologySuiteMethodCallTranslatorPlugin( /// public class NpgsqlGeometryMethodTranslator : IMethodCallTranslator { - private static readonly MethodInfo _collectionItem = typeof(GeometryCollection).GetRuntimeProperty("Item")!.GetMethod!; - private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory; private readonly IRelationalTypeMappingSource _typeMappingSource; @@ -77,11 +75,31 @@ public NpgsqlGeometryMethodTranslator( MethodInfo method, IReadOnlyList arguments, IDiagnosticsLogger logger) - => method.DeclaringType == typeof(NpgsqlNetTopologySuiteDbFunctionsExtensions) - ? TranslateDbFunction(method, arguments) - : instance is not null && typeof(Geometry).IsAssignableFrom(method.DeclaringType) - ? TranslateGeometryMethod(instance, method, arguments) - : null; + => method.DeclaringType switch + { + var t when typeof(Geometry).IsAssignableFrom(t) && instance is not null + => TranslateGeometryMethod(instance, method, arguments), + + var t when t == typeof(NpgsqlNetTopologySuiteDbFunctionsExtensions) + => TranslateDbFunction(method, arguments), + + // This handles the collection indexer (geom_collection[x] -> ST_GeometryN(geom_collection, x + 1)) + // This is needed as a special case because EF transforms the indexer into a call to Enumerable.ElementAt + var t when t == typeof(Enumerable) + && method.Name is nameof(Enumerable.ElementAt) + && method.ReturnType == typeof(Geometry) + && arguments is [var collection, var index] + && _typeMappingSource.FindMapping(typeof(Geometry), collection.TypeMapping!.StoreType) is RelationalTypeMapping geometryTypeMapping + => _sqlExpressionFactory.Function( + "ST_GeometryN", + [collection, OneBased(index)], + nullable: true, + argumentsPropagateNullability: TrueArrays[2], + method.ReturnType, + geometryTypeMapping), + + _ => null + }; private SqlExpression? TranslateDbFunction( MethodInfo method, @@ -143,17 +161,6 @@ public NpgsqlGeometryMethodTranslator( arguments = typeMappedArguments; - if (Equals(method, _collectionItem)) - { - return _sqlExpressionFactory.Function( - "ST_GeometryN", - [instance, OneBased(arguments[0])], - nullable: true, - argumentsPropagateNullability: TrueArrays[2], - method.ReturnType, - _typeMappingSource.FindMapping(typeof(Geometry), instance.TypeMapping!.StoreType)); - } - return method.Name switch { nameof(Geometry.AsBinary) @@ -226,16 +233,16 @@ SqlExpression Function(string name, SqlExpression[] arguments, Type returnType, nullable: true, argumentsPropagateNullability: TrueArrays[arguments.Length], returnType, typeMapping); - // NetTopologySuite uses 0-based indexing, but PostGIS uses 1-based - SqlExpression OneBased(SqlExpression arg) - => arg is SqlConstantExpression constant - ? _sqlExpressionFactory.Constant((int)constant.Value! + 1, constant.TypeMapping) - : _sqlExpressionFactory.Add(arg, _sqlExpressionFactory.Constant(1)); - RelationalTypeMapping ResultGeometryMapping() { Debug.Assert(typeof(Geometry).IsAssignableFrom(method.ReturnType)); return _typeMappingSource.FindMapping(method.ReturnType, storeType)!; } } + + // NetTopologySuite uses 0-based indexing, but PostGIS uses 1-based + private SqlExpression OneBased(SqlExpression arg) + => arg is SqlConstantExpression constant + ? _sqlExpressionFactory.Constant((int)constant.Value! + 1, constant.TypeMapping) + : _sqlExpressionFactory.Add(arg, _sqlExpressionFactory.Constant(1)); } diff --git a/test/EFCore.PG.FunctionalTests/Query/Translations/Temporal/DateOnlyTranslationsNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/Translations/Temporal/DateOnlyTranslationsNpgsqlTest.cs index 18865635d..3755823e8 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Translations/Temporal/DateOnlyTranslationsNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Translations/Temporal/DateOnlyTranslationsNpgsqlTest.cs @@ -69,6 +69,18 @@ WHERE floor(date_part('dow', b."DateOnly"))::int = 6 """); } + public override async Task DayNumber(bool async) + { + await base.DayNumber(async); + + AssertSql( + """ +SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" +FROM "BasicTypesEntities" AS b +WHERE b."DateOnly" - DATE '0001-01-01' = 726780 +"""); + } + public override async Task AddYears(bool async) { await base.AddYears(async); @@ -105,6 +117,20 @@ public override async Task AddDays(bool async) """); } + public override async Task DayNumber_subtraction(bool async) + { + await base.DayNumber_subtraction(async); + + AssertSql( + """ +@DayNumber='726775' + +SELECT b."Id", b."Bool", b."Byte", b."ByteArray", b."DateOnly", b."DateTime", b."DateTimeOffset", b."Decimal", b."Double", b."Enum", b."FlagsEnum", b."Float", b."Guid", b."Int", b."Long", b."Short", b."String", b."TimeOnly", b."TimeSpan" +FROM "BasicTypesEntities" AS b +WHERE (b."DateOnly" - DATE '0001-01-01') - @DayNumber = 5 +"""); + } + public override async Task FromDateTime(bool async) { await base.FromDateTime(async);