diff --git a/src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlCubeDbFunctionsExtensions.cs b/src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlCubeDbFunctionsExtensions.cs
new file mode 100644
index 000000000..fcdce89a4
--- /dev/null
+++ b/src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlCubeDbFunctionsExtensions.cs
@@ -0,0 +1,122 @@
+// ReSharper disable once CheckNamespace
+namespace Microsoft.EntityFrameworkCore;
+
+///
+/// Provides extension methods for supporting PostgreSQL translation.
+///
+public static class NpgsqlCubeDbFunctionsExtensions
+{
+ ///
+ /// Determines whether a cube overlaps with a specified cube.
+ ///
+ ///
+ ///
+ ///
+ /// true if the cubes overlap; otherwise, false.
+ ///
+ ///
+ /// is only intended for use via SQL translation as part of an EF Core LINQ query.
+ ///
+ public static bool Overlaps(this NpgsqlCube a, NpgsqlCube b)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Overlaps)));
+
+ ///
+ /// Determines whether a cube contains a specified cube.
+ ///
+ /// The cube in which to locate the specified cube.
+ /// The specified cube to locate in the cube.
+ ///
+ /// true if the cube contains the specified cube; otherwise, false.
+ ///
+ ///
+ /// is only intended for use via SQL translation as part of an EF Core LINQ query.
+ ///
+ public static bool Contains(this NpgsqlCube a, NpgsqlCube b)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Contains)));
+
+ ///
+ /// Determines whether a cube is contained by a specified cube.
+ ///
+ /// The cube to locate in the specified cube.
+ /// The specified cube in which to locate the cube.
+ ///
+ /// true if the cube is contained by the specified cube; otherwise, false.
+ ///
+ ///
+ /// is only intended for use via SQL translation as part of an EF Core LINQ query.
+ ///
+ public static bool ContainedBy(this NpgsqlCube a, NpgsqlCube b)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(ContainedBy)));
+
+ ///
+ /// Extracts the n-th coordinate of the cube (counting from 1).
+ ///
+ /// The cube from which to extract the specified coordinate.
+ /// The specified coordinate to extract from the cube.
+ ///
+ /// The n-th coordinate of the cube (counting from 1).
+ ///
+ ///
+ /// is only intended for use via SQL translation as part of an EF Core LINQ query.
+ ///
+ public static double NthCoordinate(this NpgsqlCube cube, int n)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(NthCoordinate)));
+
+ ///
+ /// 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.
+ ///
+ /// The cube from which to extract the specified coordinate.
+ /// The specified coordinate to extract from the cube.
+ ///
+ /// The n-th coordinate of the cube, counting in the following way: n = 2 * k - 1.
+ ///
+ ///
+ /// is only intended for use via SQL translation as part of an EF Core LINQ query.
+ ///
+ public static double NthCoordinate2(this NpgsqlCube cube, int n)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(NthCoordinate2)));
+
+ ///
+ /// Computes the Euclidean distance between two cubes.
+ ///
+ /// The first cube.
+ /// The second cube.
+ ///
+ /// The Euclidean distance between the specified cubes.
+ ///
+ ///
+ /// is only intended for use via SQL translation as part of an EF Core LINQ query.
+ ///
+ public static double Distance(this NpgsqlCube a, NpgsqlCube b)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Distance)));
+
+ ///
+ /// Computes the taxicab (L-1 metric) distance between two cubes.
+ ///
+ /// The first cube.
+ /// The second cube.
+ ///
+ /// The taxicab (L-1 metric) distance between the two cubes.
+ ///
+ ///
+ /// is only intended for use via SQL translation as part of an EF Core LINQ query.
+ ///
+ public static double DistanceTaxicab(this NpgsqlCube a, NpgsqlCube b)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DistanceTaxicab)));
+
+ ///
+ /// Computes the Chebyshev (L-inf metric) distance between two cubes.
+ ///
+ /// The first cube.
+ /// The second cube.
+ ///
+ /// The Chebyshev (L-inf metric) distance between the two cubes.
+ ///
+ ///
+ /// is only intended for use via SQL translation as part of an EF Core LINQ query.
+ ///
+ public static double DistanceChebyshev(this NpgsqlCube a, NpgsqlCube b)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(DistanceChebyshev)));
+}
diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCubeTranslator.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCubeTranslator.cs
new file mode 100644
index 000000000..d0c2b1c34
--- /dev/null
+++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCubeTranslator.cs
@@ -0,0 +1,61 @@
+using System.Security.AccessControl;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions;
+
+namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionTranslators.Internal;
+
+///
+/// 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.
+///
+public class NpgsqlCubeTranslator : IMethodCallTranslator
+{
+ private readonly NpgsqlSqlExpressionFactory _sqlExpressionFactory;
+
+ ///
+ /// 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.
+ ///
+ public NpgsqlCubeTranslator(NpgsqlSqlExpressionFactory sqlExpressionFactory)
+ {
+ _sqlExpressionFactory = sqlExpressionFactory;
+ }
+
+ ///
+ public SqlExpression? Translate(
+ SqlExpression? instance,
+ MethodInfo method,
+ IReadOnlyList arguments,
+ IDiagnosticsLogger 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
+ };
+ }
+}
diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMethodCallTranslatorProvider.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMethodCallTranslatorProvider.cs
index f688eed2b..d265a5f72 100644
--- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMethodCallTranslatorProvider.cs
+++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMethodCallTranslatorProvider.cs
@@ -63,6 +63,7 @@ public NpgsqlMethodCallTranslatorProvider(
new NpgsqlRowValueTranslator(sqlExpressionFactory),
new NpgsqlStringMethodTranslator(typeMappingSource, sqlExpressionFactory, model),
new NpgsqlTrigramsMethodTranslator(typeMappingSource, sqlExpressionFactory, model),
+ new NpgsqlCubeTranslator(sqlExpressionFactory),
});
}
}
diff --git a/src/EFCore.PG/Query/Expressions/PostgresExpressionType.cs b/src/EFCore.PG/Query/Expressions/PostgresExpressionType.cs
index 967b3b1aa..bb0f3a337 100644
--- a/src/EFCore.PG/Query/Expressions/PostgresExpressionType.cs
+++ b/src/EFCore.PG/Query/Expressions/PostgresExpressionType.cs
@@ -159,4 +159,28 @@ public enum PostgresExpressionType
LTreeFirstMatches, // ?~ or ?@
#endregion LTree
+
+ #region Cube
+
+ ///
+ /// Represents a PostgreSQL operator for extracting the n-th coordinate of a cube (counting from 1).
+ ///
+ CubeNthCoordinate, // ->
+
+ ///
+ /// Represents a PostgreSQL operator for extracting the n-th coordinate of a cube (by n = 2 * k - 1).
+ ///
+ CubeNthCoordinate2, // ~>
+
+ ///
+ /// Represents a PostgreSQL operator for computing the taxicab (L-1 metric) distance between two cubes.
+ ///
+ CubeDistanceTaxicab, // <#>
+
+ ///
+ /// Represents a PostgreSQL operator for computing the Chebyshev (L-inf metric) distance between two cubes.
+ ///
+ CubeDistanceChebyshev, // <=>
+
+ #endregion
}
diff --git a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs
index 05ad44e8c..1fe415b0e 100644
--- a/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs
+++ b/src/EFCore.PG/Query/Internal/NpgsqlQuerySqlGenerator.cs
@@ -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(" ");
diff --git a/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCubeTypeMapping.cs b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCubeTypeMapping.cs
new file mode 100644
index 000000000..5adc3ee49
--- /dev/null
+++ b/src/EFCore.PG/Storage/Internal/Mapping/NpgsqlCubeTypeMapping.cs
@@ -0,0 +1,52 @@
+namespace Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping;
+
+///
+/// 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.
+///
+public class NpgsqlCubeTypeMapping : NpgsqlTypeMapping
+{
+ ///
+ /// 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.
+ ///
+ public NpgsqlCubeTypeMapping() : base("cube", typeof(NpgsqlCube), NpgsqlDbType.Cube) {}
+
+ ///
+ /// 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.
+ ///
+ protected NpgsqlCubeTypeMapping(RelationalTypeMappingParameters parameters)
+ : base(parameters, NpgsqlDbType.Cube) {}
+
+ ///
+ /// 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.
+ ///
+ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
+ => new NpgsqlCubeTypeMapping(parameters);
+
+ ///
+ /// Generates the SQL representation of a non-null literal value.
+ ///
+ /// The literal value.
+ /// The generated string.
+ 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";
+ }
+}
diff --git a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs
index c398233ce..02db6b1fe 100644
--- a/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs
+++ b/src/EFCore.PG/Storage/Internal/NpgsqlTypeMappingSource.cs
@@ -183,6 +183,7 @@ static NpgsqlTypeMappingSource()
private readonly NpgsqlHstoreTypeMapping _immutableHstore = new(typeof(ImmutableDictionary));
private readonly NpgsqlTidTypeMapping _tid = new();
private readonly NpgsqlPgLsnTypeMapping _pgLsn = new();
+ private readonly NpgsqlCubeTypeMapping _cube = new();
private readonly NpgsqlLTreeTypeMapping _ltree = new();
private readonly NpgsqlStringTypeMapping _ltreeString = new("ltree", NpgsqlDbType.LTree);
@@ -336,6 +337,7 @@ public NpgsqlTypeMappingSource(
{ "lo", new[] { _lo } },
{ "tid", new[] { _tid } },
{ "pg_lsn", new[] { _pgLsn } },
+ { "cube", new[] { _cube } },
{ "int4range", new[] { _int4range } },
{ "int8range", new[] { _int8range } },
@@ -397,6 +399,7 @@ public NpgsqlTypeMappingSource(
{ typeof(Dictionary), _hstore },
{ typeof(NpgsqlTid), _tid },
{ typeof(NpgsqlLogSequenceNumber), _pgLsn },
+ { typeof(NpgsqlCube), _cube },
{ typeof(NpgsqlPoint), _point },
{ typeof(NpgsqlBox), _box },
diff --git a/test/EFCore.PG.FunctionalTests/Query/CubeQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/CubeQueryNpgsqlTest.cs
new file mode 100644
index 000000000..7ab3076d3
--- /dev/null
+++ b/test/EFCore.PG.FunctionalTests/Query/CubeQueryNpgsqlTest.cs
@@ -0,0 +1,78 @@
+using Npgsql.EntityFrameworkCore.PostgreSQL.TestUtilities;
+
+namespace Npgsql.EntityFrameworkCore.PostgreSQL.Query;
+
+public class CubeQueryNpgsqlTest : IClassFixture
+{
+ 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
+ {
+ 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 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
+}