-
Notifications
You must be signed in to change notification settings - Fork 256
Cube support (revamped) #3651
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
Merged
Merged
Cube support (revamped) #3651
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
e025414
Initial EFCore.PG support for NpgsqlCube type and methods
kirkbrauer 4c10fe8
Handle NpgsqlCube constructors via NewExpression and add more constru…
kirkbrauer e82a55b
Fix type mapping issues
kirkbrauer 05ccf53
Remove unnecessary test case for Overlaps
kirkbrauer 399b9e3
Add PgIndexes type and PgIndexesArrayExpression to represent an array…
kirkbrauer bef7a53
Implement LowerLeft and UpperRight indexing translation, remove PgInd…
kirkbrauer 9c375a4
Switch to use cube.ToString() method for SQL literal
kirkbrauer cd97729
Fix nits and improve array translation
kirkbrauer f083412
Fix additional nits
kirkbrauer ef29c19
Use existing ToSubset() method and fix nits
kirkbrauer ff8756f
Set minimum PostgreSQL version for cube tests to 14
kirkbrauer 239cee9
Re-add NpgsqlCube cast to cube GenerateNonNullSqlLiteral
kirkbrauer f0aa374
Refactor translation to construct SelectExpression directly and impro…
kirkbrauer a44e6a2
Move translation of sub-query into the expression visitor
kirkbrauer e9e90c2
Move all ToSubset() translation to a method in the expression visitor…
kirkbrauer fe6a6f2
Removed unused using statement
kirkbrauer 413706f
Bump Npgsql CI version and fix cube distance operator conflict
kirkbrauer 982d325
Remove unused using statement
kirkbrauer 8acdf7d
Fix distance operator issues and remove redundant type
kirkbrauer 62184f6
Remove cube-specific expression mapping from NpgsqlSqlExpressionFactory
kirkbrauer 00eb348
Rename TranslateToSubset to TranslateCubeToSubset
kirkbrauer 35e0c94
Fix missing newline on PgExpressionType.Distance revert
kirkbrauer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
169 changes: 169 additions & 0 deletions
169
src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlCubeDbFunctionsExtensions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| // ReSharper disable once CheckNamespace | ||
|
|
||
| using NpgsqlTypes; | ||
|
|
||
| namespace Microsoft.EntityFrameworkCore; | ||
|
|
||
| /// <summary> | ||
| /// Provides extension methods for <see cref="NpgsqlCube" /> supporting PostgreSQL translation. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// See <see href="https://www.postgresql.org/docs/current/cube.html">PostgreSQL documentation for the cube extension</see>. | ||
| /// </remarks> | ||
| public static class NpgsqlCubeDbFunctionsExtensions | ||
| { | ||
| /// <summary> | ||
| /// Determines whether two cubes overlap (have points in common). | ||
| /// </summary> | ||
| /// <param name="cube">The first cube.</param> | ||
| /// <param name="other">The second cube.</param> | ||
| /// <returns> | ||
| /// true if the cubes overlap; otherwise, false. | ||
| /// </returns> | ||
| /// <exception cref="InvalidOperationException"> | ||
| /// <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 cube, NpgsqlCube other) | ||
| => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Overlaps))); | ||
|
|
||
| /// <summary> | ||
| /// Determines whether a cube contains another cube. | ||
| /// </summary> | ||
| /// <param name="cube">The cube to check.</param> | ||
| /// <param name="other">The cube that may be contained.</param> | ||
| /// <returns> | ||
| /// true if <paramref name="cube" /> contains <paramref name="other" />; otherwise, false. | ||
| /// </returns> | ||
| /// <exception cref="InvalidOperationException"> | ||
| /// <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 cube, NpgsqlCube other) | ||
| => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Contains))); | ||
|
|
||
| /// <summary> | ||
| /// Determines whether a cube is contained by another cube. | ||
| /// </summary> | ||
| /// <param name="cube">The cube to check.</param> | ||
| /// <param name="other">The cube that may contain it.</param> | ||
| /// <returns> | ||
| /// true if <paramref name="cube" /> is contained by <paramref name="other" />; otherwise, false. | ||
| /// </returns> | ||
| /// <exception cref="InvalidOperationException"> | ||
| /// <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 cube, NpgsqlCube other) | ||
| => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainedBy))); | ||
|
|
||
| /// <summary> | ||
| /// Extracts the n-th coordinate of the cube. | ||
| /// </summary> | ||
| /// <param name="cube">The cube.</param> | ||
| /// <param name="index">The coordinate index to extract.</param> | ||
| /// <returns>The coordinate value at the specified index.</returns> | ||
| /// <remarks> | ||
| /// This method uses zero-based indexing (C# convention), which is translated to PostgreSQL's one-based indexing. | ||
| /// </remarks> | ||
| /// <exception cref="InvalidOperationException"> | ||
| /// <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 index) | ||
| => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(NthCoordinate))); | ||
|
|
||
| /// <summary> | ||
| /// Extracts the n-th coordinate of the cube for K-nearest neighbor (KNN) indexing. | ||
| /// </summary> | ||
| /// <param name="cube">The cube.</param> | ||
| /// <param name="index">The coordinate index to extract.</param> | ||
| /// <returns>The coordinate value at the specified index.</returns> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// This method uses zero-based indexing (C# convention), which is translated to PostgreSQL's one-based indexing. | ||
| /// </para> | ||
| /// <para> | ||
| /// This is the same as <see cref="NthCoordinate" /> except it is marked "lossy" for GiST indexing purposes, | ||
| /// which is useful for K-nearest neighbor queries. | ||
| /// </para> | ||
| /// </remarks> | ||
| /// <exception cref="InvalidOperationException"> | ||
| /// <see cref="NthCoordinateKnn" /> is only intended for use via SQL translation as part of an EF Core LINQ query. | ||
| /// </exception> | ||
| public static double NthCoordinateKnn(this NpgsqlCube cube, int index) | ||
| => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(NthCoordinateKnn))); | ||
|
|
||
| /// <summary> | ||
| /// Computes the Euclidean distance between two cubes. | ||
| /// </summary> | ||
| /// <param name="cube">The first cube.</param> | ||
| /// <param name="other">The second cube.</param> | ||
| /// <returns>The Euclidean distance between the two cubes.</returns> | ||
| /// <exception cref="InvalidOperationException"> | ||
| /// <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 cube, NpgsqlCube other) | ||
| => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Distance))); | ||
|
|
||
| /// <summary> | ||
| /// Computes the taxicab (L-1 metric) distance between two cubes. | ||
| /// </summary> | ||
| /// <param name="cube">The first cube.</param> | ||
| /// <param name="other">The second cube.</param> | ||
| /// <returns>The taxicab distance between the two cubes.</returns> | ||
| /// <exception cref="InvalidOperationException"> | ||
| /// <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 cube, NpgsqlCube other) | ||
| => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DistanceTaxicab))); | ||
|
|
||
| /// <summary> | ||
| /// Computes the Chebyshev (L-inf metric) distance between two cubes. | ||
| /// </summary> | ||
| /// <param name="cube">The first cube.</param> | ||
| /// <param name="other">The second cube.</param> | ||
| /// <returns>The Chebyshev distance between the two cubes.</returns> | ||
| /// <exception cref="InvalidOperationException"> | ||
| /// <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 cube, NpgsqlCube other) | ||
| => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DistanceChebyshev))); | ||
|
|
||
| /// <summary> | ||
| /// Computes the union of two cubes, producing the smallest cube that encloses both. | ||
| /// </summary> | ||
| /// <param name="cube">The first cube.</param> | ||
| /// <param name="other">The second cube.</param> | ||
| /// <returns>The smallest cube that encloses both input cubes.</returns> | ||
| /// <exception cref="InvalidOperationException"> | ||
| /// <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 cube, NpgsqlCube other) | ||
| => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Union))); | ||
|
|
||
| /// <summary> | ||
| /// Computes the intersection of two cubes. | ||
| /// </summary> | ||
| /// <param name="cube">The first cube.</param> | ||
| /// <param name="other">The second cube.</param> | ||
| /// <returns>The intersection of the two cubes.</returns> | ||
| /// <exception cref="InvalidOperationException"> | ||
| /// <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 cube, NpgsqlCube other) | ||
| => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Intersect))); | ||
|
|
||
| /// <summary> | ||
| /// Increases the size of a cube by a specified radius in at least the specified number of dimensions. | ||
| /// </summary> | ||
| /// <param name="cube">The cube to enlarge.</param> | ||
| /// <param name="radius">The amount by which to enlarge the cube (can be negative to shrink).</param> | ||
| /// <param name="dimensions">The number of dimensions to enlarge (optional, defaults to all dimensions).</param> | ||
| /// <returns>The enlarged (or shrunk) cube.</returns> | ||
| /// <remarks> | ||
| /// If the specified number of dimensions is greater than the cube's current dimensions, | ||
| /// the extra dimensions are added with the specified radius. | ||
| /// </remarks> | ||
| /// <exception cref="InvalidOperationException"> | ||
| /// <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 radius, int dimensions) | ||
| => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Enlarge))); | ||
| } |
171 changes: 171 additions & 0 deletions
171
src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCubeTranslator.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions; | ||
roji marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions.Internal; | ||
| 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( | ||
| NpgsqlSqlExpressionFactory sqlExpressionFactory, | ||
| IRelationalTypeMappingSource typeMappingSource) : IMethodCallTranslator, IMemberTranslator | ||
| { | ||
| private readonly RelationalTypeMapping _cubeTypeMapping = typeMappingSource.FindMapping(typeof(NpgsqlCube))!; | ||
| private readonly RelationalTypeMapping _doubleTypeMapping = typeMappingSource.FindMapping(typeof(double))!; | ||
|
|
||
| /// <inheritdoc /> | ||
| public virtual SqlExpression? Translate( | ||
| SqlExpression? instance, | ||
| MethodInfo method, | ||
| IReadOnlyList<SqlExpression> arguments, | ||
| IDiagnosticsLogger<DbLoggerCategory.Query> logger) | ||
| { | ||
| // Handle NpgsqlCubeDbFunctionsExtensions methods | ||
| if (method.DeclaringType != typeof(NpgsqlCubeDbFunctionsExtensions)) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| return method.Name switch | ||
| { | ||
| nameof(NpgsqlCubeDbFunctionsExtensions.Overlaps) when arguments is [var cube1, var cube2] | ||
| => sqlExpressionFactory.Overlaps(cube1, cube2), | ||
|
|
||
| nameof(NpgsqlCubeDbFunctionsExtensions.Contains) when arguments is [var cube1, var cube2] | ||
| => sqlExpressionFactory.Contains(cube1, cube2), | ||
|
|
||
| nameof(NpgsqlCubeDbFunctionsExtensions.ContainedBy) when arguments is [var cube1, var cube2] | ||
| => sqlExpressionFactory.ContainedBy(cube1, cube2), | ||
|
|
||
| nameof(NpgsqlCubeDbFunctionsExtensions.Distance) when arguments is [var cube1, var cube2] | ||
| => new PgBinaryExpression( | ||
| PgExpressionType.Distance, | ||
| sqlExpressionFactory.ApplyTypeMapping(cube1, _cubeTypeMapping), | ||
| sqlExpressionFactory.ApplyTypeMapping(cube2, _cubeTypeMapping), | ||
| typeof(double), | ||
| _doubleTypeMapping), | ||
|
|
||
| nameof(NpgsqlCubeDbFunctionsExtensions.DistanceTaxicab) when arguments is [var cube1, var cube2] | ||
| => new PgBinaryExpression( | ||
| PgExpressionType.CubeDistanceTaxicab, | ||
| sqlExpressionFactory.ApplyTypeMapping(cube1, _cubeTypeMapping), | ||
| sqlExpressionFactory.ApplyTypeMapping(cube2, _cubeTypeMapping), | ||
| typeof(double), | ||
| _doubleTypeMapping), | ||
|
|
||
| nameof(NpgsqlCubeDbFunctionsExtensions.DistanceChebyshev) when arguments is [var cube1, var cube2] | ||
| => new PgBinaryExpression( | ||
| PgExpressionType.CubeDistanceChebyshev, | ||
| sqlExpressionFactory.ApplyTypeMapping(cube1, _cubeTypeMapping), | ||
| sqlExpressionFactory.ApplyTypeMapping(cube2, _cubeTypeMapping), | ||
| typeof(double), | ||
| _doubleTypeMapping), | ||
|
|
||
| nameof(NpgsqlCubeDbFunctionsExtensions.NthCoordinate) when arguments is [var cube, var index] | ||
| => new PgBinaryExpression( | ||
| PgExpressionType.CubeNthCoordinate, | ||
| sqlExpressionFactory.ApplyTypeMapping(cube, _cubeTypeMapping), | ||
| ConvertToPostgresIndex(index), | ||
| typeof(double), | ||
| _doubleTypeMapping), | ||
|
|
||
| nameof(NpgsqlCubeDbFunctionsExtensions.NthCoordinateKnn) when arguments is [var cube, var index] | ||
| => new PgBinaryExpression( | ||
| PgExpressionType.CubeNthCoordinateKnn, | ||
| sqlExpressionFactory.ApplyTypeMapping(cube, _cubeTypeMapping), | ||
| ConvertToPostgresIndex(index), | ||
| typeof(double), | ||
| _doubleTypeMapping), | ||
|
|
||
| nameof(NpgsqlCubeDbFunctionsExtensions.Union) when arguments is [var cube1, var cube2] | ||
| => sqlExpressionFactory.Function( | ||
| "cube_union", | ||
| [cube1, cube2], | ||
| nullable: true, | ||
| argumentsPropagateNullability: TrueArrays[2], | ||
| typeof(NpgsqlCube), | ||
| typeMappingSource.FindMapping(typeof(NpgsqlCube))), | ||
|
|
||
| nameof(NpgsqlCubeDbFunctionsExtensions.Intersect) when arguments is [var cube1, var cube2] | ||
| => sqlExpressionFactory.Function( | ||
| "cube_inter", | ||
| [cube1, cube2], | ||
| nullable: true, | ||
| argumentsPropagateNullability: TrueArrays[2], | ||
| typeof(NpgsqlCube), | ||
| typeMappingSource.FindMapping(typeof(NpgsqlCube))), | ||
|
|
||
| nameof(NpgsqlCubeDbFunctionsExtensions.Enlarge) when arguments is [var cube1, var cube2, var dimension] | ||
| => sqlExpressionFactory.Function( | ||
| "cube_enlarge", | ||
| [cube1, cube2, dimension], | ||
| nullable: true, | ||
| argumentsPropagateNullability: TrueArrays[3], | ||
| typeof(NpgsqlCube), | ||
| typeMappingSource.FindMapping(typeof(NpgsqlCube))), | ||
|
|
||
| _ => null | ||
| }; | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public virtual SqlExpression? Translate( | ||
| SqlExpression? instance, | ||
| MemberInfo member, | ||
| Type returnType, | ||
| IDiagnosticsLogger<DbLoggerCategory.Query> logger) | ||
| { | ||
| if (member.DeclaringType != typeof(NpgsqlCube)) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| return member.Name switch | ||
| { | ||
| nameof(NpgsqlCube.Dimensions) | ||
| => sqlExpressionFactory.Function( | ||
| "cube_dim", | ||
| [instance!], | ||
| nullable: true, | ||
| argumentsPropagateNullability: TrueArrays[1], | ||
| typeof(int)), | ||
|
|
||
| nameof(NpgsqlCube.IsPoint) | ||
| => sqlExpressionFactory.Function( | ||
| "cube_is_point", | ||
| [instance!], | ||
| nullable: true, | ||
| argumentsPropagateNullability: TrueArrays[1], | ||
| typeof(bool)), | ||
|
|
||
| nameof(NpgsqlCube.LowerLeft) | ||
| => throw new InvalidOperationException( | ||
| $"The '{nameof(NpgsqlCube.LowerLeft)}' property cannot be translated to SQL. " + | ||
| $"To access individual lower-left coordinates in queries, use indexer syntax (e.g., cube.LowerLeft[index]) instead."), | ||
|
|
||
| nameof(NpgsqlCube.UpperRight) | ||
| => throw new InvalidOperationException( | ||
| $"The '{nameof(NpgsqlCube.UpperRight)}' property cannot be translated to SQL. " + | ||
| $"To access individual upper-right coordinates in queries, use indexer syntax (e.g., cube.UpperRight[index]) instead."), | ||
|
|
||
| _ => null | ||
| }; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Converts a zero-based index to one-based for PostgreSQL cube functions. | ||
| /// For constant indexes, simplifies at translation time to avoid unnecessary addition in SQL. | ||
| /// </summary> | ||
| private SqlExpression ConvertToPostgresIndex(SqlExpression indexExpression) | ||
| { | ||
| var intTypeMapping = typeMappingSource.FindMapping(typeof(int)); | ||
|
|
||
| return indexExpression is SqlConstantExpression { Value: int index } | ||
| ? sqlExpressionFactory.Constant(index + 1, intTypeMapping) | ||
| : sqlExpressionFactory.Add(indexExpression, sqlExpressionFactory.Constant(1, intTypeMapping)); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.