Skip to content

Commit 3a7c16d

Browse files
committed
Initial implementation of cube support
1 parent 1d6d841 commit 3a7c16d

File tree

8 files changed

+359
-13
lines changed

8 files changed

+359
-13
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// ReSharper disable once CheckNamespace
2+
namespace Microsoft.EntityFrameworkCore;
3+
4+
/// <summary>
5+
/// Provides extension methods for <see cref="NpgsqlCube"/> supporting PostgreSQL translation.
6+
/// </summary>
7+
public static class NpgsqlCubeDbFunctionsExtensions
8+
{
9+
/// <summary>
10+
/// Determines whether a cube overlaps with a specified cube.
11+
/// </summary>
12+
/// <param name="a"></param>
13+
/// <param name="b"></param>
14+
/// <returns>
15+
/// <value>true</value> if the cubes overlap; otherwise, <value>false</value>.
16+
/// </returns>
17+
/// <exception cref="NotSupportedException">
18+
/// <see cref="Overlaps" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
19+
/// </exception>
20+
public static bool Overlaps(this NpgsqlCube a, NpgsqlCube b)
21+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Overlaps)));
22+
23+
/// <summary>
24+
/// Determines whether a cube contains a specified cube.
25+
/// </summary>
26+
/// <param name="a">The cube in which to locate the specified cube.</param>
27+
/// <param name="b">The specified cube to locate in the cube.</param>
28+
/// <returns>
29+
/// <value>true</value> if the cube contains the specified cube; otherwise, <value>false</value>.
30+
/// </returns>
31+
/// <exception cref="NotSupportedException">
32+
/// <see cref="Contains" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
33+
/// </exception>
34+
public static bool Contains(this NpgsqlCube a, NpgsqlCube b)
35+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Contains)));
36+
37+
/// <summary>
38+
/// Determines whether a cube is contained by a specified cube.
39+
/// </summary>
40+
/// <param name="a">The cube to locate in the specified cube.</param>
41+
/// <param name="b">The specified cube in which to locate the cube.</param>
42+
/// <returns>
43+
/// <value>true</value> if the cube is contained by the specified cube; otherwise, <value>false</value>.
44+
/// </returns>
45+
/// <exception cref="NotSupportedException">
46+
/// <see cref="ContainedBy" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
47+
/// </exception>
48+
public static bool ContainedBy(this NpgsqlCube a, NpgsqlCube b)
49+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainedBy)));
50+
51+
/// <summary>
52+
/// Extracts the n-th coordinate of the cube (counting from 1).
53+
/// </summary>
54+
/// <param name="cube">The cube from which to extract the specified coordinate.</param>
55+
/// <param name="n">The specified coordinate to extract from the cube.</param>
56+
/// <returns>
57+
/// The n-th coordinate of the cube (counting from 1).
58+
/// </returns>
59+
/// <exception cref="NotSupportedException">
60+
/// <see cref="NthCoordinate" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
61+
/// </exception>
62+
public static double NthCoordinate(this NpgsqlCube cube, int n)
63+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(NthCoordinate)));
64+
65+
/// <summary>
66+
/// Extracts the n-th coordinate of the cube, counting in the following way: n = 2 * k - 1 means lower bound
67+
/// of k-th dimension, n = 2 * k means upper bound of k-th dimension. Negative n denotes the inverse value
68+
/// of the corresponding positive coordinate. This operator is designed for KNN-GiST support.
69+
/// </summary>
70+
/// <param name="cube">The cube from which to extract the specified coordinate.</param>
71+
/// <param name="n">The specified coordinate to extract from the cube.</param>
72+
/// <returns>
73+
/// The n-th coordinate of the cube, counting in the following way: n = 2 * k - 1.
74+
/// </returns>
75+
/// <exception cref="NotSupportedException">
76+
/// <see cref="NthCoordinate2" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
77+
/// </exception>
78+
public static double NthCoordinate2(this NpgsqlCube cube, int n)
79+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(NthCoordinate2)));
80+
81+
/// <summary>
82+
/// Computes the Euclidean distance between two cubes.
83+
/// </summary>
84+
/// <param name="a">The first cube.</param>
85+
/// <param name="b">The second cube.</param>
86+
/// <returns>
87+
/// The Euclidean distance between the specified cubes.
88+
/// </returns>
89+
/// <exception cref="NotSupportedException">
90+
/// <see cref="Distance" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
91+
/// </exception>
92+
public static double Distance(this NpgsqlCube a, NpgsqlCube b)
93+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Distance)));
94+
95+
/// <summary>
96+
/// Computes the taxicab (L-1 metric) distance between two cubes.
97+
/// </summary>
98+
/// <param name="a">The first cube.</param>
99+
/// <param name="b">The second cube.</param>
100+
/// <returns>
101+
/// The taxicab (L-1 metric) distance between the two cubes.
102+
/// </returns>
103+
/// <exception cref="NotSupportedException">
104+
/// <see cref="DistanceTaxicab" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
105+
/// </exception>
106+
public static double DistanceTaxicab(this NpgsqlCube a, NpgsqlCube b)
107+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DistanceTaxicab)));
108+
109+
/// <summary>
110+
/// Computes the Chebyshev (L-inf metric) distance between two cubes.
111+
/// </summary>
112+
/// <param name="a">The first cube.</param>
113+
/// <param name="b">The second cube.</param>
114+
/// <returns>
115+
/// The Chebyshev (L-inf metric) distance between the two cubes.
116+
/// </returns>
117+
/// <exception cref="NotSupportedException">
118+
/// <see cref="DistanceChebyshev" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
119+
/// </exception>
120+
public static double DistanceChebyshev(this NpgsqlCube a, NpgsqlCube b)
121+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DistanceChebyshev)));
122+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System.Security.AccessControl;
2+
using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions;
3+
4+
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal;
5+
6+
/// <summary>
7+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
8+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
9+
/// any release. You should only use it directly in your code with extreme caution and knowing that
10+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
11+
/// </summary>
12+
public class NpgsqlCubeTranslator : IMethodCallTranslator
13+
{
14+
private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory;
15+
16+
/// <summary>
17+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
18+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
19+
/// any release. You should only use it directly in your code with extreme caution and knowing that
20+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
21+
/// </summary>
22+
public NpgsqlCubeTranslator(NpgsqlSqlExpressionFactory sqlExpressionFactory)
23+
{
24+
_sqlExpressionFactory = sqlExpressionFactory;
25+
}
26+
27+
/// <inheritdoc />
28+
public SqlExpression? Translate(
29+
SqlExpression? instance,
30+
MethodInfo method,
31+
IReadOnlyList<SqlExpression> arguments,
32+
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
33+
{
34+
if (method.DeclaringType != typeof(NpgsqlCubeDbFunctionsExtensions))
35+
{
36+
return null;
37+
}
38+
39+
return method.Name switch
40+
{
41+
nameof(NpgsqlCubeDbFunctionsExtensions.Overlaps)
42+
=> _sqlExpressionFactory.Overlaps(arguments[0], arguments[1]),
43+
nameof(NpgsqlCubeDbFunctionsExtensions.Contains)
44+
=> _sqlExpressionFactory.Contains(arguments[0], arguments[1]),
45+
nameof(NpgsqlCubeDbFunctionsExtensions.ContainedBy)
46+
=> _sqlExpressionFactory.ContainedBy(arguments[0], arguments[1]),
47+
nameof(NpgsqlCubeDbFunctionsExtensions.NthCoordinate)
48+
=> _sqlExpressionFactory.MakePostgresBinary(PostgresExpressionType.CubeNthCoordinate, arguments[0], arguments[1]),
49+
nameof(NpgsqlCubeDbFunctionsExtensions.NthCoordinate2)
50+
=> _sqlExpressionFactory.MakePostgresBinary(PostgresExpressionType.CubeNthCoordinate2, arguments[0], arguments[1]),
51+
nameof(NpgsqlCubeDbFunctionsExtensions.Distance)
52+
=> _sqlExpressionFactory.MakePostgresBinary(PostgresExpressionType.Distance, arguments[0], arguments[1]),
53+
nameof(NpgsqlCubeDbFunctionsExtensions.DistanceTaxicab)
54+
=> _sqlExpressionFactory.MakePostgresBinary(PostgresExpressionType.CubeDistanceTaxicab, arguments[0], arguments[1]),
55+
nameof(NpgsqlCubeDbFunctionsExtensions.DistanceChebyshev)
56+
=> _sqlExpressionFactory.MakePostgresBinary(PostgresExpressionType.CubeDistanceChebyshev, arguments[0], arguments[1]),
57+
58+
_ => null
59+
};
60+
}
61+
}

src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMethodCallTranslatorProvider.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public NpgsqlMethodCallTranslatorProvider(
6363
new NpgsqlRowValueTranslator(sqlExpressionFactory),
6464
new NpgsqlStringMethodTranslator(typeMappingSource, sqlExpressionFactory, model),
6565
new NpgsqlTrigramsMethodTranslator(typeMappingSource, sqlExpressionFactory, model),
66+
new NpgsqlCubeTranslator(sqlExpressionFactory),
6667
});
6768
}
6869
}

src/EFCore.PG/Query/Expressions/PostgresExpressionType.cs

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public enum PostgresExpressionType
1111
/// Represents a PostgreSQL contains operator.
1212
/// </summary>
1313
Contains, // >> (inet/cidr), @>
14-
14+
1515
/// <summary>
1616
/// Represents a PostgreSQL contained-by operator.
1717
/// </summary>
@@ -36,12 +36,12 @@ public enum PostgresExpressionType
3636
/// Represents a PostgreSQL network contained-by-or-equal operator.
3737
/// </summary>
3838
NetworkContainedByOrEqual, // <<=
39-
39+
4040
/// <summary>
4141
/// Represents a PostgreSQL network contains-or-equal operator.
4242
/// </summary>
4343
NetworkContainsOrEqual, // >>=
44-
44+
4545
/// <summary>
4646
/// Represents a PostgreSQL network contains-or-contained-by operator.
4747
/// </summary>
@@ -55,12 +55,12 @@ public enum PostgresExpressionType
5555
/// Represents a PostgreSQL operator for checking if a range is strictly to the left of another range.
5656
/// </summary>
5757
RangeIsStrictlyLeftOf, // <<
58-
58+
5959
/// <summary>
6060
/// Represents a PostgreSQL operator for checking if a range is strictly to the right of another range.
6161
/// </summary>
6262
RangeIsStrictlyRightOf, // >>
63-
63+
6464
/// <summary>
6565
/// Represents a PostgreSQL operator for checking if a range does not extend to the right of another range.
6666
/// </summary>
@@ -75,17 +75,17 @@ public enum PostgresExpressionType
7575
/// Represents a PostgreSQL operator for checking if a range is adjacent to another range.
7676
/// </summary>
7777
RangeIsAdjacentTo, // -|-
78-
78+
7979
/// <summary>
8080
/// Represents a PostgreSQL operator for performing a union between two ranges.
8181
/// </summary>
8282
RangeUnion, // +
83-
83+
8484
/// <summary>
8585
/// Represents a PostgreSQL operator for performing an intersection between two ranges.
8686
/// </summary>
8787
RangeIntersect, // *
88-
88+
8989
/// <summary>
9090
/// Represents a PostgreSQL operator for performing an except operation between two ranges.
9191
/// </summary>
@@ -104,7 +104,7 @@ public enum PostgresExpressionType
104104
/// Represents a PostgreSQL operator for logical AND within a full-text search match.
105105
/// </summary>
106106
TextSearchAnd, // &&
107-
107+
108108
/// <summary>
109109
/// Represents a PostgreSQL operator for logical OR within a full-text search match.
110110
/// </summary>
@@ -123,7 +123,7 @@ public enum PostgresExpressionType
123123
/// Represents a PostgreSQL operator for checking whether any of multiple keys exists in a JSON document.
124124
/// </summary>
125125
JsonExistsAny, // ?@>
126-
126+
127127
/// <summary>
128128
/// Represents a PostgreSQL operator for checking whether all the given keys exist in a JSON document.
129129
/// </summary>
@@ -132,7 +132,7 @@ public enum PostgresExpressionType
132132
#endregion JSON
133133

134134
#region LTree
135-
135+
136136
/// <summary>
137137
/// Represents a PostgreSQL operator for matching in an ltree type.
138138
/// </summary>
@@ -142,7 +142,7 @@ public enum PostgresExpressionType
142142
/// Represents a PostgreSQL operator for matching in an ltree type.
143143
/// </summary>
144144
LTreeMatchesAny, // ?
145-
145+
146146
/// <summary>
147147
/// Represents a PostgreSQL operator for finding the first ancestor in an ltree type.
148148
/// </summary>
@@ -152,11 +152,35 @@ public enum PostgresExpressionType
152152
/// Represents a PostgreSQL operator for finding the first descendent in an ltree type.
153153
/// </summary>
154154
LTreeFirstDescendent, // ?<@
155-
155+
156156
/// <summary>
157157
/// Represents a PostgreSQL operator for finding the first match in an ltree type.
158158
/// </summary>
159159
LTreeFirstMatches, // ?~ or ?@
160160

161161
#endregion LTree
162+
163+
#region Cube
164+
165+
/// <summary>
166+
/// Represents a PostgreSQL operator for extracting the n-th coordinate of a cube (counting from 1).
167+
/// </summary>
168+
CubeNthCoordinate, // ->
169+
170+
/// <summary>
171+
/// Represents a PostgreSQL operator for extracting the n-th coordinate of a cube (by n = 2 * k - 1).
172+
/// </summary>
173+
CubeNthCoordinate2, // ~>
174+
175+
/// <summary>
176+
/// Represents a PostgreSQL operator for computing the taxicab (L-1 metric) distance between two cubes.
177+
/// </summary>
178+
CubeDistanceTaxicab, // <#>
179+
180+
/// <summary>
181+
/// Represents a PostgreSQL operator for computing the Chebyshev (L-inf metric) distance between two cubes.
182+
/// </summary>
183+
CubeDistanceChebyshev, // <=>
184+
185+
#endregion
162186
}

src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,11 @@ when binaryExpression.Left.TypeMapping is NpgsqlInetTypeMapping or NpgsqlCidrTyp
512512

513513
PostgresExpressionType.Distance => "<->",
514514

515+
PostgresExpressionType.CubeNthCoordinate => "->",
516+
PostgresExpressionType.CubeNthCoordinate2 => "~>",
517+
PostgresExpressionType.CubeDistanceTaxicab => "<#>",
518+
PostgresExpressionType.CubeDistanceChebyshev => "<=>",
519+
515520
_ => throw new ArgumentOutOfRangeException($"Unhandled operator type: {binaryExpression.OperatorType}")
516521
})
517522
.Append(" ");
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping;
2+
3+
/// <summary>
4+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
5+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
6+
/// any release. You should only use it directly in your code with extreme caution and knowing that
7+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
8+
/// </summary>
9+
public class NpgsqlCubeTypeMapping : NpgsqlTypeMapping
10+
{
11+
/// <summary>
12+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
13+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
14+
/// any release. You should only use it directly in your code with extreme caution and knowing that
15+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
16+
/// </summary>
17+
public NpgsqlCubeTypeMapping() : base("cube", typeof(NpgsqlCube), NpgsqlDbType.Cube) {}
18+
19+
/// <summary>
20+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
21+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
22+
/// any release. You should only use it directly in your code with extreme caution and knowing that
23+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
24+
/// </summary>
25+
protected NpgsqlCubeTypeMapping(RelationalTypeMappingParameters parameters)
26+
: base(parameters, NpgsqlDbType.Cube) {}
27+
28+
/// <summary>
29+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
30+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
31+
/// any release. You should only use it directly in your code with extreme caution and knowing that
32+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
33+
/// </summary>
34+
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
35+
=> new NpgsqlCubeTypeMapping(parameters);
36+
37+
/// <summary>
38+
/// Generates the SQL representation of a non-null literal value.
39+
/// </summary>
40+
/// <param name="value">The literal value.</param>
41+
/// <returns>The generated string.</returns>
42+
protected override string GenerateNonNullSqlLiteral(object value)
43+
{
44+
if (!(value is NpgsqlCube cube))
45+
throw new InvalidOperationException($"Can't generate a cube SQL literal for CLR type {value.GetType()}");
46+
47+
if (cube.Point)
48+
return $"'({string.Join(",", cube.LowerLeft)})'::cube";
49+
else
50+
return $"'({string.Join(",", cube.LowerLeft)}),({string.Join(",", cube.UpperRight)})'::cube";
51+
}
52+
}

0 commit comments

Comments
 (0)