diff --git a/Directory.Packages.props b/Directory.Packages.props index 1a5f1f0dc..dc3a3b92a 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,7 @@ 7.0.0-preview.7.22359.1 7.0.0-preview.6.22354.1 - 7.0.0-preview.5 + 7.0.0-preview.6-ci.20220710T074055 diff --git a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteAggregateMethodCallTranslatorPlugin.cs b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteAggregateMethodCallTranslatorPlugin.cs index a55ead297..91ee33ebc 100644 --- a/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteAggregateMethodCallTranslatorPlugin.cs +++ b/src/EFCore.PG.NTS/Query/ExpressionTranslators/Internal/NpgsqlNetTopologySuiteAggregateMethodCallTranslatorPlugin.cs @@ -36,6 +36,9 @@ private static readonly MethodInfo ConvexHullMethod private static readonly MethodInfo UnionMethod = typeof(UnaryUnionOp).GetRuntimeMethod(nameof(UnaryUnionOp.Union), new[] { typeof(IEnumerable) })!; + private static readonly MethodInfo EnvelopeCombineMethod + = typeof(EnvelopeCombiner).GetRuntimeMethod(nameof(EnvelopeCombiner.CombineAsGeometry), new[] { typeof(IEnumerable) })!; + private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory; private readonly IRelationalTypeMappingSource _typeMappingSource; @@ -51,44 +54,65 @@ public NpgsqlNetTopologySuiteAggregateMethodTranslator( MethodInfo method, EnumerableExpression source, IReadOnlyList arguments, IDiagnosticsLogger logger) { - if (source.Selector is not SqlExpression sqlExpression - || (method != GeometryCombineMethod && method != UnionMethod && method != ConvexHullMethod)) + if (source.Selector is not SqlExpression sqlExpression) { return null; } - var resultTypeMapping = _typeMappingSource.FindMapping(typeof(Geometry), sqlExpression.TypeMapping?.StoreType ?? "geometry"); - - if (method == GeometryCombineMethod || method == UnionMethod) + if (method == ConvexHullMethod) { - return _sqlExpressionFactory.AggregateFunction( - method == GeometryCombineMethod ? "ST_Collect" : "ST_Union", - new[] { sqlExpression }, - source, + // PostGIS has no built-in aggregate convex hull, but we can simply apply ST_Collect beforehand as recommended in the docs + // https://postgis.net/docs/ST_ConvexHull.html + return _sqlExpressionFactory.Function( + "ST_ConvexHull", + new[] + { + _sqlExpressionFactory.AggregateFunction( + "ST_Collect", + new[] { sqlExpression }, + source, + nullable: true, + argumentsPropagateNullability: new[] { false }, + typeof(Geometry), + GetMapping()) + }, nullable: true, - argumentsPropagateNullability: new[] { false }, + argumentsPropagateNullability: new[] { true }, typeof(Geometry), - resultTypeMapping); + GetMapping()); } - // PostGIS has no built-in aggregate convex hull, but we can simply apply ST_Collect beforehand as recommended in the docs - // https://postgis.net/docs/ST_ConvexHull.html - return _sqlExpressionFactory.Function( - "ST_ConvexHull", - new[] - { + if (method == EnvelopeCombineMethod) + { + // ST_Extent returns a PostGIS box2d, which isn't a geometry and has no binary output function. + // Convert it to a geometry first. + return _sqlExpressionFactory.Convert( _sqlExpressionFactory.AggregateFunction( - "ST_Collect", + "ST_Extent", new[] { sqlExpression }, source, nullable: true, argumentsPropagateNullability: new[] { false }, typeof(Geometry), - resultTypeMapping) - }, - nullable: true, - argumentsPropagateNullability: new[] { true }, - typeof(Geometry), - resultTypeMapping); + GetMapping()), + typeof(Geometry), GetMapping()); + } + + if (method == UnionMethod || method == GeometryCombineMethod) + { + return _sqlExpressionFactory.AggregateFunction( + method == UnionMethod ? "ST_Union" : "ST_Collect", + new[] { sqlExpression }, + source, + nullable: true, + argumentsPropagateNullability: new[] { false }, + typeof(Geometry), + GetMapping()); + } + + return null; + + RelationalTypeMapping? GetMapping() + => _typeMappingSource.FindMapping(typeof(Geometry), sqlExpression.TypeMapping?.StoreType ?? "geometry"); } } diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs b/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs index 767c55e3f..a452d9055 100644 --- a/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs +++ b/src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs @@ -47,7 +47,9 @@ protected override DbConnection CreateDbConnection() if (ProvidePasswordCallback is not null) { +#pragma warning disable 618 // ProvidePasswordCallback is obsolete conn.ProvidePasswordCallback = ProvidePasswordCallback; +#pragma warning restore 618 } return conn; diff --git a/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeographyTest.cs b/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeographyTest.cs index dbbadde7b..0055ed5c9 100644 --- a/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeographyTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeographyTest.cs @@ -260,6 +260,7 @@ public override async Task ToText(bool async) public override Task Boundary(bool async) => Task.CompletedTask; public override Task Combine_aggregate(bool async) => Task.CompletedTask; + public override Task EnvelopeCombine_aggregate(bool async) => Task.CompletedTask; public override Task Contains(bool async) => Task.CompletedTask; public override Task ConvexHull(bool async) => Task.CompletedTask; public override Task ConvexHull_aggregate(bool async) => Task.CompletedTask; diff --git a/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeometryTest.cs b/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeometryTest.cs index 240f970da..3d7875a50 100644 --- a/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeometryTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/SpatialQueryNpgsqlGeometryTest.cs @@ -91,6 +91,17 @@ public override async Task Combine_aggregate(bool async) GROUP BY p.""Group"""); } + public override async Task EnvelopeCombine_aggregate(bool async) + { + await base.EnvelopeCombine_aggregate(async); + + AssertSql( + @"SELECT p.""Group"" AS ""Id"", ST_Extent(p.""Point"")::geometry AS ""Combined"" +FROM ""PointEntity"" AS p +WHERE (p.""Point"" IS NOT NULL) +GROUP BY p.""Group"""); + } + public override async Task Contains(bool async) { await base.Contains(async);