Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Translate spatial aggregates #28115

Merged
1 commit merged into from
Jun 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ private static void AppendLiteral(StoreObjectIdentifier storeObject, IndentedStr
.Append("DbFunction(").Append(code.Literal(storeObject.Name)).Append(")");
break;
default:
Check.DebugAssert(false, "Unexpected StoreObjectType: " + storeObject.StoreObjectType);
Check.DebugFail("Unexpected StoreObjectType: " + storeObject.StoreObjectType);
break;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public static readonly IDictionary<Type, ServiceCharacteristics> RelationalServi
new ServiceCharacteristics(ServiceLifetime.Singleton, multipleRegistrations: true)
},
{ typeof(IMethodCallTranslatorPlugin), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) },
{ typeof(IAggregateMethodCallTranslatorPlugin), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) },
{ typeof(IMemberTranslatorPlugin), new ServiceCharacteristics(ServiceLifetime.Scoped, multipleRegistrations: true) }
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ protected virtual IReadOnlyList<MigrationOperation> Sort(
else
{
leftovers.Add(operation);
Check.DebugAssert(false, "Unexpected operation type: " + operation.GetType());
Check.DebugFail("Unexpected operation type: " + operation.GetType());
}
}

Expand Down Expand Up @@ -275,7 +275,7 @@ protected virtual IReadOnlyList<MigrationOperation> Sort(
}
else
{
Check.DebugAssert(false, "Operation removed twice: " + cyclicAddForeignKeyOperation);
Check.DebugFail("Operation removed twice: " + cyclicAddForeignKeyOperation);
}
}

Expand Down Expand Up @@ -2189,7 +2189,7 @@ private IEnumerable<MigrationOperation> GetDataOperations(

if (forSource)
{
Check.DebugAssert(false, "Insert using the source model");
Check.DebugFail("Insert using the source model");
break;
}

Expand All @@ -2212,7 +2212,7 @@ private IEnumerable<MigrationOperation> GetDataOperations(

if (forSource)
{
Check.DebugAssert(false, "Update using the source model");
Check.DebugFail("Update using the source model");
break;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ public virtual Expression Expand(
return _visitedFromSqlExpressions[fromSql] = fromSql.Update(Expression.Constant(constantValues, typeof(object[])));

default:
Check.DebugAssert(false, "FromSql.Arguments must be Constant/ParameterExpression");
Check.DebugFail("FromSql.Arguments must be Constant/ParameterExpression");
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public static IServiceCollection AddEntityFrameworkSqlServerNetTopologySuite(
new EntityFrameworkRelationalServicesBuilder(serviceCollection)
.TryAdd<IRelationalTypeMappingSourcePlugin, SqlServerNetTopologySuiteTypeMappingSourcePlugin>()
.TryAdd<IMethodCallTranslatorPlugin, SqlServerNetTopologySuiteMethodCallTranslatorPlugin>()
.TryAdd<IAggregateMethodCallTranslatorPlugin, SqlServerNetTopologySuiteAggregateMethodCallTranslatorPlugin>()
.TryAdd<IMemberTranslatorPlugin, SqlServerNetTopologySuiteMemberTranslatorPlugin>()
.TryAdd<IEvaluatableExpressionFilterPlugin, SqlServerNetTopologySuiteEvaluatableExpressionFilterPlugin>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,12 @@ public SqlServerGeometryMethodTranslator(
arguments.Where(e => typeof(Geometry).IsAssignableFrom(e.Type)));
var typeMapping = ExpressionExtensions.InferTypeMapping(geometryExpressions.ToArray());

Check.DebugAssert(typeMapping != null, "At least one argument must have typeMapping.");
if (typeMapping is null)
{
Check.DebugFail("At least one argument must have typeMapping.");
return null;
}

var storeType = typeMapping.StoreType;
var isGeography = string.Equals(storeType, "geography", StringComparison.OrdinalIgnoreCase);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.SqlServer.Query.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 SqlServerNetTopologySuiteAggregateMethodCallTranslatorPlugin : IAggregateMethodCallTranslatorPlugin
{
/// <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 SqlServerNetTopologySuiteAggregateMethodCallTranslatorPlugin(
IRelationalTypeMappingSource typeMappingSource,
ISqlExpressionFactory sqlExpressionFactory)
{
Translators = new IAggregateMethodCallTranslator[]
{
new SqlServerNetTopologySuiteAggregateMethodTranslator(sqlExpressionFactory, typeMappingSource)
};
}

/// <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 virtual IEnumerable<IAggregateMethodCallTranslator> Translators { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using NetTopologySuite.Algorithm;
using NetTopologySuite.Geometries;
using NetTopologySuite.Geometries.Utilities;
using NetTopologySuite.Operation.Union;

namespace Microsoft.EntityFrameworkCore.SqlServer.Query.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 SqlServerNetTopologySuiteAggregateMethodTranslator : IAggregateMethodCallTranslator
{
private static readonly MethodInfo GeometryCombineMethod
= typeof(GeometryCombiner).GetRuntimeMethod(nameof(GeometryCombiner.Combine), new[] { typeof(IEnumerable<Geometry>) })!;

private static readonly MethodInfo ConvexHullMethod
= typeof(ConvexHull).GetRuntimeMethod(nameof(ConvexHull.Create), new[] { typeof(IEnumerable<Geometry>) })!;

private static readonly MethodInfo UnionMethod
= typeof(UnaryUnionOp).GetRuntimeMethod(nameof(UnaryUnionOp.Union), new[] { typeof(IEnumerable<Geometry>) })!;
bricelam marked this conversation as resolved.
Show resolved Hide resolved

private readonly ISqlExpressionFactory _sqlExpressionFactory;
private readonly IRelationalTypeMappingSource _typeMappingSource;

/// <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 SqlServerNetTopologySuiteAggregateMethodTranslator(
ISqlExpressionFactory sqlExpressionFactory,
IRelationalTypeMappingSource typeMappingSource)
{
_sqlExpressionFactory = sqlExpressionFactory;
_typeMappingSource = typeMappingSource;
}

/// <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 virtual SqlExpression? Translate(
MethodInfo method, EnumerableExpression source, IReadOnlyList<SqlExpression> arguments,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
// Docs: https://docs.microsoft.com/sql/t-sql/spatial-geometry/static-aggregate-geometry-methods

if (source.Selector is not SqlExpression sqlExpression)
{
return null;
}

if (sqlExpression.TypeMapping is not { } typeMapping)
{
Check.DebugFail("SQL expression is missing a type mapping.");
return null;
}

var functionName = method == GeometryCombineMethod
roji marked this conversation as resolved.
Show resolved Hide resolved
? "CollectionAggregate"
: method == UnionMethod
? "UnionAggregate"
: method == ConvexHullMethod
? "ConvexHullAggregate"
: null;

if (functionName is null)
{
return null;
}

if (source.Predicate != null)
{
sqlExpression = _sqlExpressionFactory.Case(
new List<CaseWhenClause> { new(source.Predicate, sqlExpression) },
elseResult: null);
}

if (source.IsDistinct)
{
sqlExpression = new DistinctExpression(sqlExpression);
}

return _sqlExpressionFactory.Function(
$"{typeMapping.StoreType}::{functionName}",
new[] { sqlExpression },
nullable: true,
argumentsPropagateNullability: new[] { false },
roji marked this conversation as resolved.
Show resolved Hide resolved
method.ReturnType,
_typeMappingSource.FindMapping(method.ReturnType, typeMapping.StoreType));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public static IServiceCollection AddEntityFrameworkSqliteNetTopologySuite(
new EntityFrameworkRelationalServicesBuilder(serviceCollection)
.TryAdd<IRelationalTypeMappingSourcePlugin, SqliteNetTopologySuiteTypeMappingSourcePlugin>()
.TryAdd<IMethodCallTranslatorPlugin, SqliteNetTopologySuiteMethodCallTranslatorPlugin>()
.TryAdd<IAggregateMethodCallTranslatorPlugin, SqliteNetTopologySuiteAggregateMethodCallTranslatorPlugin>()
.TryAdd<IMemberTranslatorPlugin, SqliteNetTopologySuiteMemberTranslatorPlugin>();

return serviceCollection;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.EntityFrameworkCore.Sqlite.Query.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 SqliteNetTopologySuiteAggregateMethodCallTranslatorPlugin : IAggregateMethodCallTranslatorPlugin
{
/// <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 SqliteNetTopologySuiteAggregateMethodCallTranslatorPlugin(
ISqlExpressionFactory sqlExpressionFactory,
IRelationalTypeMappingSource typeMappingSource)
{
Translators = new IAggregateMethodCallTranslator[]
{
new SqliteNetTopologySuiteAggregateMethodTranslator(sqlExpressionFactory, typeMappingSource)
};
}

/// <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 virtual IEnumerable<IAggregateMethodCallTranslator> Translators { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using NetTopologySuite.Algorithm;
using NetTopologySuite.Geometries;
using NetTopologySuite.Geometries.Utilities;
using NetTopologySuite.Operation.Union;

namespace Microsoft.EntityFrameworkCore.Sqlite.Query.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 SqliteNetTopologySuiteAggregateMethodTranslator : IAggregateMethodCallTranslator
{
private static readonly MethodInfo GeometryCombineMethod
= typeof(GeometryCombiner).GetRuntimeMethod(nameof(GeometryCombiner.Combine), new[] { typeof(IEnumerable<Geometry>) })!;

private static readonly MethodInfo ConvexHullMethod
= typeof(ConvexHull).GetRuntimeMethod(nameof(ConvexHull.Create), new[] { typeof(IEnumerable<Geometry>) })!;

private static readonly MethodInfo UnionMethod
= typeof(UnaryUnionOp).GetRuntimeMethod(nameof(UnaryUnionOp.Union), new[] { typeof(IEnumerable<Geometry>) })!;

private readonly ISqlExpressionFactory _sqlExpressionFactory;
private readonly IRelationalTypeMappingSource _typeMappingSource;

/// <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 SqliteNetTopologySuiteAggregateMethodTranslator(
ISqlExpressionFactory sqlExpressionFactory,
IRelationalTypeMappingSource typeMappingSource)
{
_sqlExpressionFactory = sqlExpressionFactory;
_typeMappingSource = typeMappingSource;
}

/// <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 virtual SqlExpression? Translate(
MethodInfo method, EnumerableExpression source, IReadOnlyList<SqlExpression> arguments,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
if (source.Selector is not SqlExpression sqlExpression
|| (method != GeometryCombineMethod && method != UnionMethod && method != ConvexHullMethod))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use different patterns in 2 providers in same PR. 😉

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only because in SQLite we need to compose the additional Collect before doing ST_ConvexHull...

{
return null;
}

if (source.Predicate != null)
{
sqlExpression = _sqlExpressionFactory.Case(
new List<CaseWhenClause> { new(source.Predicate, sqlExpression) },
elseResult: null);
}

if (source.IsDistinct)
{
sqlExpression = new DistinctExpression(sqlExpression);
}

if (method == GeometryCombineMethod || method == UnionMethod)
{
return _sqlExpressionFactory.Function(
method == GeometryCombineMethod ? "Collect" : "GUnion",
new[] { sqlExpression },
nullable: true,
argumentsPropagateNullability: new[] { false },
typeof(Geometry));
}

// Spatialite has no built-in aggregate convex hull, but we can simply apply Collect beforehand
bricelam marked this conversation as resolved.
Show resolved Hide resolved
return _sqlExpressionFactory.Function(
"ConvexHull",
new[]
{
_sqlExpressionFactory.Function(
"Collect",
new[] { sqlExpression },
nullable: true,
argumentsPropagateNullability: new[] { false },
bricelam marked this conversation as resolved.
Show resolved Hide resolved
typeof(Geometry))
},
nullable: true,
argumentsPropagateNullability: new[] { true },
typeof(Geometry));
}
}
Loading