Skip to content

Commit

Permalink
Translate EnvelopeCombiner.CombineAsGeometry (#2425)
Browse files Browse the repository at this point in the history
Closes #2384
  • Loading branch information
roji committed Jul 10, 2022
1 parent 0d85c2c commit a352409
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 25 deletions.
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<EFCoreVersion>7.0.0-preview.7.22359.1</EFCoreVersion>
<MicrosoftExtensionsVersion>7.0.0-preview.6.22354.1</MicrosoftExtensionsVersion>
<NpgsqlVersion>7.0.0-preview.5</NpgsqlVersion>
<NpgsqlVersion>7.0.0-preview.6-ci.20220710T074055</NpgsqlVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ private static readonly MethodInfo ConvexHullMethod
private static readonly MethodInfo UnionMethod
= typeof(UnaryUnionOp).GetRuntimeMethod(nameof(UnaryUnionOp.Union), new[] { typeof(IEnumerable<Geometry>) })!;

private static readonly MethodInfo EnvelopeCombineMethod
= typeof(EnvelopeCombiner).GetRuntimeMethod(nameof(EnvelopeCombiner.CombineAsGeometry), new[] { typeof(IEnumerable<Geometry>) })!;

private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory;
private readonly IRelationalTypeMappingSource _typeMappingSource;

Expand All @@ -51,44 +54,65 @@ public NpgsqlNetTopologySuiteAggregateMethodTranslator(
MethodInfo method, EnumerableExpression source, IReadOnlyList<SqlExpression> arguments,
IDiagnosticsLogger<DbLoggerCategory.Query> 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");
}
}
2 changes: 2 additions & 0 deletions src/EFCore.PG/Storage/Internal/NpgsqlRelationalConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit a352409

Please sign in to comment.