Skip to content

Commit e130ecd

Browse files
committed
Add more NpgsqlCube translation
1 parent e854129 commit e130ecd

4 files changed

Lines changed: 208 additions & 26 deletions

File tree

src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlCubeDbFunctionsExtensions.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,53 @@ public static double DistanceTaxicab(this NpgsqlCube a, NpgsqlCube b)
119119
/// </exception>
120120
public static double DistanceChebyshev(this NpgsqlCube a, NpgsqlCube b)
121121
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DistanceChebyshev)));
122+
123+
/// <summary>
124+
/// Produces the union of two cubes.
125+
/// </summary>
126+
/// <param name="a">The first cube.</param>
127+
/// <param name="b">The second cube.</param>
128+
/// <returns>
129+
/// The union of the two cubes.
130+
/// </returns>
131+
/// <exception cref="NotSupportedException">
132+
/// <see cref="Union" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
133+
/// </exception>
134+
public static NpgsqlCube Union(this NpgsqlCube a, NpgsqlCube b)
135+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Union)));
136+
137+
/// <summary>
138+
/// Produces the intersection of two cubes.
139+
/// </summary>
140+
/// <param name="a">The first cube.</param>
141+
/// <param name="b">The second cube.</param>
142+
/// <returns>
143+
/// The intersection of the two cubes.
144+
/// </returns>
145+
/// <exception cref="NotSupportedException">
146+
/// <see cref="Intersect" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
147+
/// </exception>
148+
public static NpgsqlCube Intersect(this NpgsqlCube a, NpgsqlCube b)
149+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Intersect)));
150+
151+
/// <summary>
152+
/// Produces a cube enlarged by the specified radius r in at least n dimensions. If the radius is negative
153+
/// the cube is shrunk instead. All defined dimensions are changed by the radius r. Lower-left coordinates are
154+
/// decreased by r and upper-right coordinates are increased by r. If a lower-left coordinate is increased to more
155+
/// than the corresponding upper-right coordinate (this can only happen when r &lt; 0) than both coordinates are set
156+
/// to their average. If n is greater than the number of defined dimensions and the cube is being enlarged (r &gt; 0),
157+
/// then extra dimensions are added to make n altogether; 0 is used as the initial value for the extra coordinates.
158+
/// This function is useful for creating bounding boxes around a point for searching for nearby points.
159+
/// </summary>
160+
/// <param name="cube">The cube to enlarge.</param>
161+
/// <param name="r">The radius by which to increase the size of the cube.</param>
162+
/// <param name="n">The number of dimensions in which to increase the size of the cube.</param>
163+
/// <returns>
164+
/// A cube enlarged by the specified radius in at least the specified number of dimensions.
165+
/// </returns>
166+
/// <exception cref="NotSupportedException">
167+
/// <see cref="Enlarge" /> is only intended for use via SQL translation as part of an EF Core LINQ query.
168+
/// </exception>
169+
public static NpgsqlCube Enlarge(this NpgsqlCube cube, double r, int n)
170+
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Enlarge)));
122171
}

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

Lines changed: 107 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using System.Security.AccessControl;
2-
using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions;
1+
using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions;
2+
using static Npgsql.EntityFrameworkCore.PostgreSQL.Utilities.Statics;
33

44
namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal;
55

@@ -9,7 +9,7 @@ namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Inte
99
/// any release. You should only use it directly in your code with extreme caution and knowing that
1010
/// doing so can result in application failures when updating to a new Entity Framework Core release.
1111
/// </summary>
12-
public class NpgsqlCubeTranslator : IMethodCallTranslator
12+
public class NpgsqlCubeTranslator : IMethodCallTranslator, IMemberTranslator
1313
{
1414
private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory;
1515

@@ -31,30 +31,115 @@ public NpgsqlCubeTranslator(NpgsqlSqlExpressionFactory sqlExpressionFactory)
3131
IReadOnlyList<SqlExpression> arguments,
3232
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
3333
{
34-
if (method.DeclaringType != typeof(NpgsqlCubeDbFunctionsExtensions))
34+
if (method.DeclaringType == typeof(NpgsqlCubeDbFunctionsExtensions))
3535
{
36-
return null;
36+
return method.Name switch
37+
{
38+
nameof(NpgsqlCubeDbFunctionsExtensions.Overlaps)
39+
=> _sqlExpressionFactory.Overlaps(arguments[0], arguments[1]),
40+
nameof(NpgsqlCubeDbFunctionsExtensions.Contains)
41+
=> _sqlExpressionFactory.Contains(arguments[0], arguments[1]),
42+
nameof(NpgsqlCubeDbFunctionsExtensions.ContainedBy)
43+
=> _sqlExpressionFactory.ContainedBy(arguments[0], arguments[1]),
44+
nameof(NpgsqlCubeDbFunctionsExtensions.NthCoordinate)
45+
=> _sqlExpressionFactory.MakePostgresBinary(PostgresExpressionType.CubeNthCoordinate, arguments[0], arguments[1]),
46+
nameof(NpgsqlCubeDbFunctionsExtensions.NthCoordinate2)
47+
=> _sqlExpressionFactory.MakePostgresBinary(PostgresExpressionType.CubeNthCoordinate2, arguments[0], arguments[1]),
48+
nameof(NpgsqlCubeDbFunctionsExtensions.Distance)
49+
=> _sqlExpressionFactory.MakePostgresBinary(PostgresExpressionType.Distance, arguments[0], arguments[1]),
50+
nameof(NpgsqlCubeDbFunctionsExtensions.DistanceTaxicab)
51+
=> _sqlExpressionFactory.MakePostgresBinary(PostgresExpressionType.CubeDistanceTaxicab, arguments[0], arguments[1]),
52+
nameof(NpgsqlCubeDbFunctionsExtensions.DistanceChebyshev)
53+
=> _sqlExpressionFactory.MakePostgresBinary(PostgresExpressionType.CubeDistanceChebyshev, arguments[0], arguments[1]),
54+
nameof(NpgsqlCubeDbFunctionsExtensions.Union)
55+
=> _sqlExpressionFactory.Function(
56+
"cube_union",
57+
new[] { arguments[0], arguments[1] },
58+
nullable: true,
59+
argumentsPropagateNullability: TrueArrays[2],
60+
method.ReturnType),
61+
nameof(NpgsqlCubeDbFunctionsExtensions.Intersect)
62+
=> _sqlExpressionFactory.Function(
63+
"cube_inter",
64+
new[] { arguments[0], arguments[1] },
65+
nullable: true,
66+
argumentsPropagateNullability: TrueArrays[2],
67+
method.ReturnType),
68+
nameof(NpgsqlCubeDbFunctionsExtensions.Enlarge)
69+
=> _sqlExpressionFactory.Function(
70+
"cube_enlarge",
71+
new[] { arguments[0], arguments[1], arguments[2] },
72+
nullable: true,
73+
argumentsPropagateNullability: TrueArrays[3],
74+
method.ReturnType),
75+
76+
_ => null
77+
};
3778
}
3879

39-
return method.Name switch
80+
if (method.DeclaringType == typeof(NpgsqlCube) && instance != null)
4081
{
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]),
82+
return method.Name switch
83+
{
84+
nameof(NpgsqlCube.LlCoord)
85+
=> _sqlExpressionFactory.Function(
86+
"cube_ll_coord",
87+
new[] { instance, arguments[0] },
88+
nullable: true,
89+
argumentsPropagateNullability: TrueArrays[2],
90+
method.ReturnType),
91+
nameof(NpgsqlCube.UrCoord)
92+
=> _sqlExpressionFactory.Function(
93+
"cube_ur_coord",
94+
new[] { instance, arguments[0] },
95+
nullable: true,
96+
argumentsPropagateNullability: TrueArrays[2],
97+
method.ReturnType),
98+
nameof(NpgsqlCube.Subset)
99+
=> _sqlExpressionFactory.Function(
100+
"cube_subset",
101+
new[] { instance, arguments[0] },
102+
nullable: true,
103+
argumentsPropagateNullability: TrueArrays[2],
104+
method.ReturnType),
105+
106+
_ => null
107+
};
108+
}
109+
110+
// TODO: Implement indexing into lower/upper lists with cube_ll_coord and cube_ur_coord
57111

112+
return null;
113+
}
114+
115+
/// <inheritdoc />
116+
public SqlExpression? Translate(
117+
SqlExpression? instance,
118+
MemberInfo member,
119+
Type returnType,
120+
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
121+
{
122+
if (member.DeclaringType != typeof(NpgsqlCube) || instance == null)
123+
{
124+
return null;
125+
}
126+
127+
return member.Name switch
128+
{
129+
nameof(NpgsqlCube.Dimensions)
130+
=> _sqlExpressionFactory.Function(
131+
"cube_dim",
132+
new[] { instance },
133+
nullable: true,
134+
argumentsPropagateNullability: TrueArrays[1],
135+
returnType),
136+
nameof(NpgsqlCube.Point)
137+
=> _sqlExpressionFactory.Function(
138+
"cube_is_point",
139+
new[] { instance },
140+
nullable: true,
141+
argumentsPropagateNullability: TrueArrays[1],
142+
returnType),
58143
_ => null
59144
};
60145
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public NpgsqlMemberTranslatorProvider(
4545
new NpgsqlRangeTranslator(typeMappingSource, sqlExpressionFactory, model, supportsMultiranges),
4646
new NpgsqlStringMemberTranslator(sqlExpressionFactory),
4747
new NpgsqlTimeSpanMemberTranslator(sqlExpressionFactory),
48+
new NpgsqlCubeTranslator(sqlExpressionFactory),
4849
});
4950
}
5051
}

test/EFCore.PG.FunctionalTests/Query/CubeQueryNpgsqlTest.cs

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,59 @@ public CubeQueryNpgsqlTest(CubeQueryNpgqlFixture fixture)
1818
public void Contains_value()
1919
{
2020
using var context = CreateContext();
21-
var result = context.CubeTestEntities.Where(x => x.Cube.Contains(new NpgsqlCube(new[] { 0.0, 0.0, 0.0 })));
22-
var sql = result.ToQueryString();
23-
Assert.Equal(1, result.Single().Id);
21+
var result = context.CubeTestEntities.Single(x => x.Cube.Contains(new NpgsqlCube(new[] { 0.0, 0.0, 0.0 })));
22+
Assert.Equal(1, result.Id);
23+
AssertSql("""
24+
SELECT c."Id", c."Cube"
25+
FROM "CubeTestEntities" AS c
26+
WHERE c."Cube" @> '(0,0,0)'::cube
27+
LIMIT 2
28+
""");
2429
}
2530

26-
#endregion
31+
[ConditionalFact]
32+
public void Subset()
33+
{
34+
using var context = CreateContext();
35+
var result = context.CubeTestEntities.Where(x => x.Id == 1).Select(x => x.Cube.Subset(1)).Single();
36+
Assert.Equal(new NpgsqlCube(-1, 1), result);
37+
AssertSql("""
38+
SELECT cube_subset(c."Cube", ARRAY[1]::integer[])
39+
FROM "CubeTestEntities" AS c
40+
WHERE c."Id" = 1
41+
LIMIT 2
42+
""");
43+
}
44+
45+
[ConditionalFact]
46+
public void Dimensions()
47+
{
48+
using var context = CreateContext();
49+
var result = context.CubeTestEntities.Where(x => x.Id == 1).Select(x => x.Cube.Dimensions).Single();
50+
Assert.Equal(3, result);
51+
AssertSql("""
52+
SELECT cube_dim(c."Cube")
53+
FROM "CubeTestEntities" AS c
54+
WHERE c."Id" = 1
55+
LIMIT 2
56+
""");
57+
}
58+
59+
[ConditionalFact]
60+
public void Is_point()
61+
{
62+
using var context = CreateContext();
63+
var result = context.CubeTestEntities.Single(x => x.Cube.Point);
64+
Assert.Equal(2, result.Id);
65+
AssertSql("""
66+
SELECT c."Id", c."Cube"
67+
FROM "CubeTestEntities" AS c
68+
WHERE cube_is_point(c."Cube")
69+
LIMIT 2
70+
""");
71+
}
72+
73+
#endregion
2774

2875
public class CubeQueryNpgqlFixture : SharedStoreFixtureBase<CubeContext>
2976
{

0 commit comments

Comments
 (0)