diff --git a/src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlCubeDbFunctionsExtensions.cs b/src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlCubeDbFunctionsExtensions.cs
new file mode 100644
index 000000000..cabfa6e7a
--- /dev/null
+++ b/src/EFCore.PG/Extensions/DbFunctionsExtensions/NpgsqlCubeDbFunctionsExtensions.cs
@@ -0,0 +1,171 @@
+// 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)));
+
+ ///
+ /// Produces the union of two cubes.
+ ///
+ /// The first cube.
+ /// The second cube.
+ ///
+ /// The union of the two cubes.
+ ///
+ ///
+ /// is only intended for use via SQL translation as part of an EF Core LINQ query.
+ ///
+ public static NpgsqlCube Union(this NpgsqlCube a, NpgsqlCube b)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Union)));
+
+ ///
+ /// Produces the intersection of two cubes.
+ ///
+ /// The first cube.
+ /// The second cube.
+ ///
+ /// The intersection of the two cubes.
+ ///
+ ///
+ /// is only intended for use via SQL translation as part of an EF Core LINQ query.
+ ///
+ public static NpgsqlCube Intersect(this NpgsqlCube a, NpgsqlCube b)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Intersect)));
+
+ ///
+ /// Produces a cube enlarged by the specified radius r in at least n dimensions. If the radius is negative
+ /// the cube is shrunk instead. All defined dimensions are changed by the radius r. Lower-left coordinates are
+ /// decreased by r and upper-right coordinates are increased by r. If a lower-left coordinate is increased to more
+ /// than the corresponding upper-right coordinate (this can only happen when r < 0) than both coordinates are set
+ /// to their average. If n is greater than the number of defined dimensions and the cube is being enlarged (r > 0),
+ /// then extra dimensions are added to make n altogether; 0 is used as the initial value for the extra coordinates.
+ /// This function is useful for creating bounding boxes around a point for searching for nearby points.
+ ///
+ /// The cube to enlarge.
+ /// The radius by which to increase the size of the cube.
+ /// The number of dimensions in which to increase the size of the cube.
+ ///
+ /// A cube enlarged by the specified radius in at least the specified number of dimensions.
+ ///
+ ///
+ /// is only intended for use via SQL translation as part of an EF Core LINQ query.
+ ///
+ public static NpgsqlCube Enlarge(this NpgsqlCube cube, double r, int n)
+ => throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Enlarge)));
+}
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..6d1ccc10d
--- /dev/null
+++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlCubeTranslator.cs
@@ -0,0 +1,146 @@
+using Npgsql.EntityFrameworkCore.PostgreSQL.Query.Expressions;
+using static Npgsql.EntityFrameworkCore.PostgreSQL.Utilities.Statics;
+
+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, IMemberTranslator
+{
+ 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 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]),
+ nameof(NpgsqlCubeDbFunctionsExtensions.Union)
+ => _sqlExpressionFactory.Function(
+ "cube_union",
+ new[] { arguments[0], arguments[1] },
+ nullable: true,
+ argumentsPropagateNullability: TrueArrays[2],
+ method.ReturnType),
+ nameof(NpgsqlCubeDbFunctionsExtensions.Intersect)
+ => _sqlExpressionFactory.Function(
+ "cube_inter",
+ new[] { arguments[0], arguments[1] },
+ nullable: true,
+ argumentsPropagateNullability: TrueArrays[2],
+ method.ReturnType),
+ nameof(NpgsqlCubeDbFunctionsExtensions.Enlarge)
+ => _sqlExpressionFactory.Function(
+ "cube_enlarge",
+ new[] { arguments[0], arguments[1], arguments[2] },
+ nullable: true,
+ argumentsPropagateNullability: TrueArrays[3],
+ method.ReturnType),
+
+ _ => null
+ };
+ }
+
+ if (method.DeclaringType == typeof(NpgsqlCube) && instance != null)
+ {
+ return method.Name switch
+ {
+ nameof(NpgsqlCube.LlCoord)
+ => _sqlExpressionFactory.Function(
+ "cube_ll_coord",
+ new[] { instance, arguments[0] },
+ nullable: true,
+ argumentsPropagateNullability: TrueArrays[2],
+ method.ReturnType),
+ nameof(NpgsqlCube.UrCoord)
+ => _sqlExpressionFactory.Function(
+ "cube_ur_coord",
+ new[] { instance, arguments[0] },
+ nullable: true,
+ argumentsPropagateNullability: TrueArrays[2],
+ method.ReturnType),
+ nameof(NpgsqlCube.Subset)
+ => _sqlExpressionFactory.Function(
+ "cube_subset",
+ new[] { instance, arguments[0] },
+ nullable: true,
+ argumentsPropagateNullability: TrueArrays[2],
+ method.ReturnType),
+
+ _ => null
+ };
+ }
+
+ // TODO: Implement indexing into lower/upper lists with cube_ll_coord and cube_ur_coord
+
+ return null;
+ }
+
+ ///
+ public SqlExpression? Translate(
+ SqlExpression? instance,
+ MemberInfo member,
+ Type returnType,
+ IDiagnosticsLogger logger)
+ {
+ if (member.DeclaringType != typeof(NpgsqlCube) || instance == null)
+ {
+ return null;
+ }
+
+ return member.Name switch
+ {
+ nameof(NpgsqlCube.Dimensions)
+ => _sqlExpressionFactory.Function(
+ "cube_dim",
+ new[] { instance },
+ nullable: true,
+ argumentsPropagateNullability: TrueArrays[1],
+ returnType),
+ nameof(NpgsqlCube.Point)
+ => _sqlExpressionFactory.Function(
+ "cube_is_point",
+ new[] { instance },
+ nullable: true,
+ argumentsPropagateNullability: TrueArrays[1],
+ returnType),
+ _ => null
+ };
+ }
+}
diff --git a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMemberTranslatorProvider.cs b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMemberTranslatorProvider.cs
index ed4159dc4..926207760 100644
--- a/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMemberTranslatorProvider.cs
+++ b/src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlMemberTranslatorProvider.cs
@@ -45,6 +45,7 @@ public NpgsqlMemberTranslatorProvider(
new NpgsqlRangeTranslator(typeMappingSource, sqlExpressionFactory, model, supportsMultiranges),
new NpgsqlStringMemberTranslator(sqlExpressionFactory),
new NpgsqlTimeSpanMemberTranslator(sqlExpressionFactory),
+ new NpgsqlCubeTranslator(sqlExpressionFactory),
});
}
}
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..e9dae3a5a
--- /dev/null
+++ b/test/EFCore.PG.FunctionalTests/Query/CubeQueryNpgsqlTest.cs
@@ -0,0 +1,125 @@
+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.Single(x => x.Cube.Contains(new NpgsqlCube(new[] { 0.0, 0.0, 0.0 })));
+ Assert.Equal(1, result.Id);
+ AssertSql("""
+ SELECT c."Id", c."Cube"
+ FROM "CubeTestEntities" AS c
+ WHERE c."Cube" @> '(0,0,0)'::cube
+ LIMIT 2
+ """);
+ }
+
+ [ConditionalFact]
+ public void Subset()
+ {
+ using var context = CreateContext();
+ var result = context.CubeTestEntities.Where(x => x.Id == 1).Select(x => x.Cube.Subset(1)).Single();
+ Assert.Equal(new NpgsqlCube(-1, 1), result);
+ AssertSql("""
+ SELECT cube_subset(c."Cube", ARRAY[1]::integer[])
+ FROM "CubeTestEntities" AS c
+ WHERE c."Id" = 1
+ LIMIT 2
+ """);
+ }
+
+ [ConditionalFact]
+ public void Dimensions()
+ {
+ using var context = CreateContext();
+ var result = context.CubeTestEntities.Where(x => x.Id == 1).Select(x => x.Cube.Dimensions).Single();
+ Assert.Equal(3, result);
+ AssertSql("""
+ SELECT cube_dim(c."Cube")
+ FROM "CubeTestEntities" AS c
+ WHERE c."Id" = 1
+ LIMIT 2
+ """);
+ }
+
+ [ConditionalFact]
+ public void Is_point()
+ {
+ using var context = CreateContext();
+ var result = context.CubeTestEntities.Single(x => x.Cube.Point);
+ Assert.Equal(2, result.Id);
+ AssertSql("""
+ SELECT c."Id", c."Cube"
+ FROM "CubeTestEntities" AS c
+ WHERE cube_is_point(c."Cube")
+ LIMIT 2
+ """);
+ }
+
+#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
+}