-
Notifications
You must be signed in to change notification settings - Fork 226
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial implementation of cube support
- Loading branch information
Showing
8 changed files
with
346 additions
and
0 deletions.
There are no files selected for viewing
122 changes: 122 additions & 0 deletions
122
src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlCubeDbFunctionsExtensions.cs
This file contains 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,122 @@ | ||
// 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))); | ||
} |
61 changes: 61 additions & 0 deletions
61
src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCubeTranslator.cs
This file contains 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,61 @@ | ||
using System.Security.AccessControl; | ||
using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions; | ||
|
||
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 | ||
{ | ||
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 null; | ||
} | ||
|
||
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]), | ||
|
||
_ => null | ||
}; | ||
} | ||
} |
This file contains 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 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 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
52 changes: 52 additions & 0 deletions
52
src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCubeTypeMapping.cs
This file contains 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,52 @@ | ||
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping; | ||
|
||
/// <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 NpgsqlCubeTypeMapping : NpgsqlTypeMapping | ||
{ | ||
/// <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 NpgsqlCubeTypeMapping() : base("cube", typeof(NpgsqlCube), NpgsqlDbType.Cube) {} | ||
|
||
/// <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> | ||
protected NpgsqlCubeTypeMapping(RelationalTypeMappingParameters parameters) | ||
: base(parameters, NpgsqlDbType.Cube) {} | ||
|
||
/// <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> | ||
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) | ||
=> new NpgsqlCubeTypeMapping(parameters); | ||
|
||
/// <summary> | ||
/// Generates the SQL representation of a non-null literal value. | ||
/// </summary> | ||
/// <param name="value">The literal value.</param> | ||
/// <returns>The generated string.</returns> | ||
protected override string GenerateNonNullSqlLiteral(object value) | ||
{ | ||
if (!(value is NpgsqlCube cube)) | ||
throw new InvalidOperationException($"Can't generate a cube SQL literal for CLR type {value.GetType()}"); | ||
|
||
if (cube.Point) | ||
return $"'({string.Join(",", cube.LowerLeft)})'::cube"; | ||
else | ||
return $"'({string.Join(",", cube.LowerLeft)}),({string.Join(",", cube.UpperRight)})'::cube"; | ||
} | ||
} |
This file contains 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
78 changes: 78 additions & 0 deletions
78
test/EFCore.PG.FunctionalTests/Query/CubeQueryNpgsqlTest.cs
This file contains 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,78 @@ | ||
using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities; | ||
|
||
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query; | ||
|
||
public class CubeQueryNpgsqlTest : IClassFixture<CubeQueryNpgsqlTest.CubeQueryNpgqlFixture> | ||
{ | ||
public CubeQueryNpgqlFixture Fixture { get; } | ||
|
||
public CubeQueryNpgsqlTest(CubeQueryNpgqlFixture fixture) | ||
{ | ||
Fixture = fixture; | ||
Fixture.TestSqlLoggerFactory.Clear(); | ||
} | ||
|
||
#region Operators | ||
|
||
[ConditionalFact] | ||
public void Contains_value() | ||
{ | ||
using var context = CreateContext(); | ||
var result = context.CubeTestEntities.Where(x => x.Cube.Contains(new NpgsqlCube(new[] { 0.0, 0.0, 0.0 }))); | ||
var sql = result.ToQueryString(); | ||
Assert.Equal(1, result.Single().Id); | ||
} | ||
|
||
#endregion | ||
|
||
public class CubeQueryNpgqlFixture : SharedStoreFixtureBase<CubeContext> | ||
{ | ||
protected override string StoreName => "CubeQueryTest"; | ||
protected override ITestStoreFactory TestStoreFactory => NpgsqlTestStoreFactory.Instance; | ||
public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; | ||
protected override void Seed(CubeContext context) => CubeContext.Seed(context); | ||
} | ||
|
||
public class CubeContext : PoolableDbContext | ||
{ | ||
public DbSet<CubeTestEntity> CubeTestEntities { get; set; } | ||
|
||
public CubeContext(DbContextOptions options) : base(options) { } | ||
|
||
protected override void OnModelCreating(ModelBuilder builder) | ||
=> builder.HasPostgresExtension("cube"); | ||
|
||
public static void Seed(CubeContext context) | ||
{ | ||
context.CubeTestEntities.AddRange( | ||
new CubeTestEntity | ||
{ | ||
Id = 1, | ||
Cube = new NpgsqlCube(new[] { -1.0, -1.0, -1.0 }, new[] { 1.0, 1.0, 1.0 }) | ||
}, | ||
new CubeTestEntity | ||
{ | ||
Id = 2, | ||
Cube = new NpgsqlCube(new []{ 1.0, 1.0, 1.0 }) | ||
}); | ||
|
||
context.SaveChanges(); | ||
} | ||
} | ||
|
||
public class CubeTestEntity | ||
{ | ||
public int Id { get; set; } | ||
|
||
public NpgsqlCube Cube { get; set; } | ||
} | ||
|
||
#region Helpers | ||
|
||
protected CubeContext CreateContext() => Fixture.CreateContext(); | ||
|
||
private void AssertSql(params string[] expected) | ||
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected); | ||
|
||
#endregion | ||
} |