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

Cube support #2808

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
@@ -0,0 +1,171 @@
// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore;

/// <summary>
/// Provides extension methods for <see cref="NpgsqlCube"/> supporting PostgreSQL translation.
/// </summary>
public static class NpgsqlCubeDbFunctionsExtensions
{
/// <summary>
/// Determines whether a cube overlaps with a specified cube.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns>
/// <value>true</value> if the cubes overlap; otherwise, <value>false</value>.
/// </returns>
/// <exception cref="NotSupportedException">
/// <see cref="Overlaps" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
/// </exception>
public static bool Overlaps(this NpgsqlCube a, NpgsqlCube b)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Overlaps)));

/// <summary>
/// Determines whether a cube contains a specified cube.
/// </summary>
/// <param name="a">The cube in which to locate the specified cube.</param>
/// <param name="b">The specified cube to locate in the cube.</param>
/// <returns>
/// <value>true</value> if the cube contains the specified cube; otherwise, <value>false</value>.
/// </returns>
/// <exception cref="NotSupportedException">
/// <see cref="Contains" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
/// </exception>
public static bool Contains(this NpgsqlCube a, NpgsqlCube b)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Contains)));

/// <summary>
/// Determines whether a cube is contained by a specified cube.
/// </summary>
/// <param name="a">The cube to locate in the specified cube.</param>
/// <param name="b">The specified cube in which to locate the cube.</param>
/// <returns>
/// <value>true</value> if the cube is contained by the specified cube; otherwise, <value>false</value>.
/// </returns>
/// <exception cref="NotSupportedException">
/// <see cref="ContainedBy" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
/// </exception>
public static bool ContainedBy(this NpgsqlCube a, NpgsqlCube b)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainedBy)));

/// <summary>
/// Extracts the n-th coordinate of the cube (counting from 1).
/// </summary>
/// <param name="cube">The cube from which to extract the specified coordinate.</param>
/// <param name="n">The specified coordinate to extract from the cube.</param>
/// <returns>
/// The n-th coordinate of the cube (counting from 1).
/// </returns>
/// <exception cref="NotSupportedException">
/// <see cref="NthCoordinate" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
/// </exception>
public static double NthCoordinate(this NpgsqlCube cube, int n)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(NthCoordinate)));

/// <summary>
/// Extracts the n-th coordinate of the cube, counting in the following way: n = 2 * k - 1 means lower bound
/// of k-th dimension, n = 2 * k means upper bound of k-th dimension. Negative n denotes the inverse value
/// of the corresponding positive coordinate. This operator is designed for KNN-GiST support.
/// </summary>
/// <param name="cube">The cube from which to extract the specified coordinate.</param>
/// <param name="n">The specified coordinate to extract from the cube.</param>
/// <returns>
/// The n-th coordinate of the cube, counting in the following way: n = 2 * k - 1.
/// </returns>
/// <exception cref="NotSupportedException">
/// <see cref="NthCoordinate2" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
/// </exception>
public static double NthCoordinate2(this NpgsqlCube cube, int n)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(NthCoordinate2)));

/// <summary>
/// Computes the Euclidean distance between two cubes.
/// </summary>
/// <param name="a">The first cube.</param>
/// <param name="b">The second cube.</param>
/// <returns>
/// The Euclidean distance between the specified cubes.
/// </returns>
/// <exception cref="NotSupportedException">
/// <see cref="Distance" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
/// </exception>
public static double Distance(this NpgsqlCube a, NpgsqlCube b)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Distance)));

/// <summary>
/// Computes the taxicab (L-1 metric) distance between two cubes.
/// </summary>
/// <param name="a">The first cube.</param>
/// <param name="b">The second cube.</param>
/// <returns>
/// The taxicab (L-1 metric) distance between the two cubes.
/// </returns>
/// <exception cref="NotSupportedException">
/// <see cref="DistanceTaxicab" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
/// </exception>
public static double DistanceTaxicab(this NpgsqlCube a, NpgsqlCube b)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DistanceTaxicab)));

/// <summary>
/// Computes the Chebyshev (L-inf metric) distance between two cubes.
/// </summary>
/// <param name="a">The first cube.</param>
/// <param name="b">The second cube.</param>
/// <returns>
/// The Chebyshev (L-inf metric) distance between the two cubes.
/// </returns>
/// <exception cref="NotSupportedException">
/// <see cref="DistanceChebyshev" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
/// </exception>
public static double DistanceChebyshev(this NpgsqlCube a, NpgsqlCube b)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DistanceChebyshev)));

/// <summary>
/// Produces the union of two cubes.
/// </summary>
/// <param name="a">The first cube.</param>
/// <param name="b">The second cube.</param>
/// <returns>
/// The union of the two cubes.
/// </returns>
/// <exception cref="NotSupportedException">
/// <see cref="Union" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
/// </exception>
public static NpgsqlCube Union(this NpgsqlCube a, NpgsqlCube b)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Union)));

/// <summary>
/// Produces the intersection of two cubes.
/// </summary>
/// <param name="a">The first cube.</param>
/// <param name="b">The second cube.</param>
/// <returns>
/// The intersection of the two cubes.
/// </returns>
/// <exception cref="NotSupportedException">
/// <see cref="Intersect" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
/// </exception>
public static NpgsqlCube Intersect(this NpgsqlCube a, NpgsqlCube b)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Intersect)));

/// <summary>
/// Produces a cube enlarged by the specified radius r in at least n dimensions. If the radius is negative
/// the cube is shrunk instead. All defined dimensions are changed by the radius r. Lower-left coordinates are
/// decreased by r and upper-right coordinates are increased by r. If a lower-left coordinate is increased to more
/// than the corresponding upper-right coordinate (this can only happen when r &lt; 0) than both coordinates are set
/// to their average. If n is greater than the number of defined dimensions and the cube is being enlarged (r &gt; 0),
/// then extra dimensions are added to make n altogether; 0 is used as the initial value for the extra coordinates.
/// This function is useful for creating bounding boxes around a point for searching for nearby points.
/// </summary>
/// <param name="cube">The cube to enlarge.</param>
/// <param name="r">The radius by which to increase the size of the cube.</param>
/// <param name="n">The number of dimensions in which to increase the size of the cube.</param>
/// <returns>
/// A cube enlarged by the specified radius in at least the specified number of dimensions.
/// </returns>
/// <exception cref="NotSupportedException">
/// <see cref="Enlarge" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
/// </exception>
public static NpgsqlCube Enlarge(this NpgsqlCube cube, double r, int n)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Enlarge)));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions;
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 NpgsqlCubeTranslator : IMethodCallTranslator, IMemberTranslator
{
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 NpgsqlCubeTranslator(NpgsqlSqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}

/// <inheritdoc />
public SqlExpression? Translate(
SqlExpression? instance,
MethodInfo method,
IReadOnlyList<SqlExpression> arguments,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
if (method.DeclaringType == typeof(NpgsqlCubeDbFunctionsExtensions))
{
return method.Name switch
{
nameof(NpgsqlCubeDbFunctionsExtensions.Overlaps)
=> _sqlExpressionFactory.Overlaps(arguments[0], arguments[1]),
nameof(NpgsqlCubeDbFunctionsExtensions.Contains)
=> _sqlExpressionFactory.Contains(arguments[0], arguments[1]),
nameof(NpgsqlCubeDbFunctionsExtensions.ContainedBy)
=> _sqlExpressionFactory.ContainedBy(arguments[0], arguments[1]),
nameof(NpgsqlCubeDbFunctionsExtensions.NthCoordinate)
=> _sqlExpressionFactory.MakePostgresBinary(PostgresExpressionType.CubeNthCoordinate, arguments[0], arguments[1]),
nameof(NpgsqlCubeDbFunctionsExtensions.NthCoordinate2)
=> _sqlExpressionFactory.MakePostgresBinary(PostgresExpressionType.CubeNthCoordinate2, arguments[0], arguments[1]),
nameof(NpgsqlCubeDbFunctionsExtensions.Distance)
=> _sqlExpressionFactory.MakePostgresBinary(PostgresExpressionType.Distance, arguments[0], arguments[1]),
nameof(NpgsqlCubeDbFunctionsExtensions.DistanceTaxicab)
=> _sqlExpressionFactory.MakePostgresBinary(PostgresExpressionType.CubeDistanceTaxicab, arguments[0], arguments[1]),
nameof(NpgsqlCubeDbFunctionsExtensions.DistanceChebyshev)
=> _sqlExpressionFactory.MakePostgresBinary(PostgresExpressionType.CubeDistanceChebyshev, arguments[0], arguments[1]),
nameof(NpgsqlCubeDbFunctionsExtensions.Union)
=> _sqlExpressionFactory.Function(
"cube_union",
new[] { arguments[0], arguments[1] },
nullable: true,
argumentsPropagateNullability: TrueArrays[2],
method.ReturnType),
nameof(NpgsqlCubeDbFunctionsExtensions.Intersect)
=> _sqlExpressionFactory.Function(
"cube_inter",
new[] { arguments[0], arguments[1] },
nullable: true,
argumentsPropagateNullability: TrueArrays[2],
method.ReturnType),
nameof(NpgsqlCubeDbFunctionsExtensions.Enlarge)
=> _sqlExpressionFactory.Function(
"cube_enlarge",
new[] { arguments[0], arguments[1], arguments[2] },
nullable: true,
argumentsPropagateNullability: TrueArrays[3],
method.ReturnType),

_ => null
};
}

if (method.DeclaringType == typeof(NpgsqlCube) && instance != null)
{
return method.Name switch
{
nameof(NpgsqlCube.LlCoord)
=> _sqlExpressionFactory.Function(
"cube_ll_coord",
new[] { instance, arguments[0] },
nullable: true,
argumentsPropagateNullability: TrueArrays[2],
method.ReturnType),
nameof(NpgsqlCube.UrCoord)
=> _sqlExpressionFactory.Function(
"cube_ur_coord",
new[] { instance, arguments[0] },
nullable: true,
argumentsPropagateNullability: TrueArrays[2],
method.ReturnType),
nameof(NpgsqlCube.Subset)
=> _sqlExpressionFactory.Function(
"cube_subset",
new[] { instance, arguments[0] },
nullable: true,
argumentsPropagateNullability: TrueArrays[2],
method.ReturnType),

_ => null
};
}

// TODO: Implement indexing into lower/upper lists with cube_ll_coord and cube_ur_coord

return null;
}

/// <inheritdoc />
public SqlExpression? Translate(
SqlExpression? instance,
MemberInfo member,
Type returnType,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
if (member.DeclaringType != typeof(NpgsqlCube) || instance == null)
{
return null;
}

return member.Name switch
{
nameof(NpgsqlCube.Dimensions)
=> _sqlExpressionFactory.Function(
"cube_dim",
new[] { instance },
nullable: true,
argumentsPropagateNullability: TrueArrays[1],
returnType),
nameof(NpgsqlCube.Point)
=> _sqlExpressionFactory.Function(
"cube_is_point",
new[] { instance },
nullable: true,
argumentsPropagateNullability: TrueArrays[1],
returnType),
_ => null
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public NpgsqlMemberTranslatorProvider(
new NpgsqlRangeTranslator(typeMappingSource, sqlExpressionFactory, model, supportsMultiranges),
new NpgsqlStringMemberTranslator(sqlExpressionFactory),
new NpgsqlTimeSpanMemberTranslator(sqlExpressionFactory),
new NpgsqlCubeTranslator(sqlExpressionFactory),
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public NpgsqlMethodCallTranslatorProvider(
new NpgsqlRowValueTranslator(sqlExpressionFactory),
new NpgsqlStringMethodTranslator(typeMappingSource, sqlExpressionFactory, model),
new NpgsqlTrigramsMethodTranslator(typeMappingSource, sqlExpressionFactory, model),
new NpgsqlCubeTranslator(sqlExpressionFactory),
});
}
}
24 changes: 24 additions & 0 deletions src/EFCore.PG/Query/Expressions/PostgresExpressionType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,28 @@ public enum PostgresExpressionType
LTreeFirstMatches, // ?~ or ?@

#endregion LTree

#region Cube

/// <summary>
/// Represents a PostgreSQL operator for extracting the n-th coordinate of a cube (counting from 1).
/// </summary>
CubeNthCoordinate, // ->

/// <summary>
/// Represents a PostgreSQL operator for extracting the n-th coordinate of a cube (by n = 2 * k - 1).
/// </summary>
CubeNthCoordinate2, // ~>

/// <summary>
/// Represents a PostgreSQL operator for computing the taxicab (L-1 metric) distance between two cubes.
/// </summary>
CubeDistanceTaxicab, // <#>

/// <summary>
/// Represents a PostgreSQL operator for computing the Chebyshev (L-inf metric) distance between two cubes.
/// </summary>
CubeDistanceChebyshev, // <=>

#endregion
}
5 changes: 5 additions & 0 deletions src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,11 @@ when binaryExpression.Left.TypeMapping is NpgsqlInetTypeMapping or NpgsqlCidrTyp

PostgresExpressionType.Distance => "<->",

PostgresExpressionType.CubeNthCoordinate => "->",
PostgresExpressionType.CubeNthCoordinate2 => "~>",
PostgresExpressionType.CubeDistanceTaxicab => "<#>",
PostgresExpressionType.CubeDistanceChebyshev => "<=>",

_ => throw new ArgumentOutOfRangeException($"Unhandled operator type: {binaryExpression.OperatorType}")
})
.Append(" ");
Expand Down
Loading