-
Notifications
You must be signed in to change notification settings - Fork 225
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Map range operations for NpgsqlRange<T> #323
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this fast and high-quality work, it looks great!
- I'm not 100% sure of the value of providing in-memory implementations of the range operations. For general, non-PostgreSQL-specific types/operations (like string regex), this makes a lot of sense; it allows the same code to client-evaluate on one database and server-evaluate on another. However, NpgsqlRange is by its nature limited to PostgreSQL only, and I'm not sure where and why someone would want to do these operations in-memory... What do you think?
- We can decide to make
IComparable<T>
IComparable<T>
, but I think we should do it only if we end up providing in-memory implementations of all operations, which I'm not sure about (see above). In any case, the value of this would be somewhat limited, considering we can express comparison with the other operations (greater than, lesser than). - Regarding
range @> range = TRUE
, see DefaultQuerySqlGenerator not flexible enough for provider override dotnet/efcore#9143. There's nothing to be done about it at the moment, but there's also no real issue with the= TRUE
either.
src/EFCore.PG/EFCore.PG.csproj
Outdated
@@ -1,5 +1,4 @@ | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
|
|||
<PropertyGroup> | |||
<VersionPrefix>2.1.0-preview2</VersionPrefix> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please undo the whitespace changes to the csproj?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IDE settings gone awry...fixed
@@ -27,6 +27,7 @@ | |||
using System.Text; | |||
using System.Text.RegularExpressions; | |||
using JetBrains.Annotations; | |||
using NpgsqlTypes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove
/// <summary> | ||
/// Caches runtime method information for <see cref="NpgsqlRangeExtensions.Equal{T}(NpgsqlRange{T}, NpgsqlRange{T})"/>. | ||
/// </summary> | ||
private static readonly MethodInfo Equal; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I don't put private
as it's the default, the code is terser that way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed default modifiers
<ItemGroup> | ||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.1.0-preview2-30183" /> | ||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.1.0-preview2-30183" /> | ||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational.Specification.Tests" Version="2.1.0-preview2-30183" /> | ||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.0-preview2-30183" /> | ||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="2.1.0-preview2-30183" /> | ||
</ItemGroup> | ||
|
||
</Project> | ||
</Project> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please undo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IDE settings gone awry...fixed
[Pure] | ||
public static bool Equal<T>(this NpgsqlRange<T> a, NpgsqlRange<T> b) where T : IComparable<T> | ||
{ | ||
throw new NotImplementedException(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please convert all methods in this file to be expression-bodied
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Single-line methods converted
@@ -152,7 +159,7 @@ void GenerateArrayIndex([NotNull] BinaryExpression expression) | |||
{ | |||
// bytea cannot be subscripted, but there's get_byte | |||
VisitSqlFunction(new SqlFunctionExpression("get_byte", typeof(byte), | |||
new[] { expression.Left, expression.Right })); | |||
new[] { expression.Left, expression.Right })); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please undo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IDE strikes again -- reverted
@@ -162,7 +169,7 @@ void GenerateArrayIndex([NotNull] BinaryExpression expression) | |||
// PostgreSQL substr() is 1-based. | |||
|
|||
VisitSqlFunction(new SqlFunctionExpression("substr", typeof(char), | |||
new[] { expression.Left, expression.Right, Expression.Constant(1) })); | |||
new[] { expression.Left, expression.Right, Expression.Constant(1) })); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please undo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IDE strikes again -- reverted
@@ -206,14 +230,17 @@ public Expression VisitRegexMatch([NotNull] RegexMatchExpression regexMatchExpre | |||
} | |||
|
|||
Sql.Append("('(?"); | |||
if (options.HasFlag(RegexOptions.IgnoreCase)) { | |||
if (options.HasFlag(RegexOptions.IgnoreCase)) | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please undo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IDE strikes again -- reverted
@@ -0,0 +1,173 @@ | |||
using System.Linq; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's unify all the range tests into one RangeQueryNpgsqlTest
, there's simply not enough of them to clutter the Query folder. You can still use regions inside to distinguish between the different operations etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved tests into RangeQueryNpgsqlTest
@@ -44,8 +43,7 @@ public void GenerateSqlLiteral_returns_timestamptz_datetime_literal() | |||
mapping.GenerateSqlLiteral(new DateTime(1997, 12, 17, 7, 37, 16, DateTimeKind.Unspecified))); | |||
|
|||
var offset = TimeZoneInfo.Local.BaseUtcOffset.Hours; | |||
var offsetStr = offset < 10 ? $"0{offset}" : offset.ToString(); | |||
Assert.StartsWith($"TIMESTAMPTZ '1997-12-17 07:37:16+{offsetStr}", | |||
Assert.StartsWith($"TIMESTAMPTZ '1997-12-17 07:37:16{offset:+00;-00}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please take this commit out of this PR? I'll review #322 separately...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure -- reverted
OK great, let me know when you want me to take another look. |
Updates
In-memory implementations:
|
Thanks for all the changes and the reaction speed.
This kind of thing is frequently done by settings constraints on the PostgreSQL side (i.e. that a given value is within the range), but I imagine in some scenarios some in-memory operations could be useful.
I don't think that's quite necessary - we're discussing extension methods that come from the Npgsql EF Core provider, it's quite clear that their first purpose is mapping to SQL operations. There are also several other cases where similar operations throw. In parallel to this there's an implementation of PostgreSQL text search (#315). Short of an in-memory reimplemention of the entire whole text search engine, including linguistic capabilities, methods there will have to throw when executed in memory... Of course the complexity of ranges is nowhere near that of full text search, but this is a good example of where an in-memory implementation simply isn't relevant.
OK. Don't forget that you can still do EF Core in REPL, but sure, if you're just playing around with the types and their implementation then it's useful to have in-memory.
That's an important point - if the in-memory implementation is going to differ in any way from the PostgreSQL implementation, then it's better not to have it at all (better to have a clear exception than a subtle behavior change). One more objection I haven't made before... Keep in mind that in theory PostgreSQL allows user-defined ranges over any type - including types which on the CLR side may be mapped to non-comparable types. It's true that at this point only the built-in range types are supported (int4range, int8range, numrange, tsrange, tstzrange, daterange), but that may change in the future. Another even worst possibility is for the ordering of element types to be somehow subtly different between CLR and PostgreSQL, so that the in-memory implementation would return different results than the PostgreSQL one - this would be totally beyond your control. So there are risks to having an in-memory implementation. That being said, for ranges specifically I think the complexity and risks are lower than other cases, and if you want to fully implement this I'm OK with that.
OK, but let's distinguish between equality (==/Equals) and comparison (
There's something I may be missing... If you add This can lead to a different way of looking at this... If you really intend to provide all the in-memory implementations as said above, that can be done on the What do you think? If you want to go down this route I think I'd be fine with that. We would probably release this in a patch version of 3.2, to allow EF Core 2.1 to still use Npgsql 3.2 and not force a dependency on 3.3. Great progress so far! |
|
||
bool testLower = compareLower > 0 || compareLower == 0 && range.LowerBoundIsInclusive; | ||
bool testUpper = compareUpper > 0 || compareUpper == 0 && range.UpperBoundIsInclusive; | ||
bool lower = a.LowerBoundInfinite || !b.LowerBoundInfinite && compareLower > 0 || compareLower == 0 && a.LowerBoundIsInclusive || !b.LowerBoundIsInclusive; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally add parentheses in cases like this because I'm incapable of remembering precedence and it's better to be 100% unambiguous...
This most recent commit reworks the unit tests to run through a wider range of cases. In doing so, I found that one of the containment cases fails when the containing range (e.g. This query returns true (version 9.6): SELECT '[1,10)'::int4range @> '(0,5)'::int4range; This seems like it will be difficult to match with purely generic functions. Some thoughts:
Hoping that I've missed the obvious solution... Any ideas? |
I understand. The situation seems even more complex, since whether this containment is true depends on whether the underlying type is discrete or not... In other words, for a range of float/double the above would be false, since '(0,5)' includes 0.5, which isn't included in One way around this without specific checks inside those methods (i.e. But this seems like a lot of trouble for an in-memory implementation, and there's also a good risk of some edge case discrepancies... I'm even less sure now that it's a good idea to do this. |
I agree that the edge case risk is significant... While I still want to see in-memory support, the known edge cases will take time to get right (and I'm sure that others will be found in the process). To keep things moving for EF Core 2.1, I'll split off the in-memory implementations from the translations, and move the current in-memory work into a new branch, I'll submit separate PRs as each in-memory operator achieves parity with the Postgres version. I think this will make it easier to spot and contain edge cases during review. Next steps
|
I think that's a good idea. The one problem is that if you are going to do in-memory, there's the option of putting the methods on the NpgsqlRange type itself - inside Npgsql - rather than as extensions (I mentioned this possibility above). Of course this makes no sense if we end up dropping in-memory. But in any case, we can start like this and see where it leads us. |
@austindrenski don't forget to ping me when you want me to review again. |
@austindrenski EF Core 2.1 is around the corner, and I'd like to close whatever outstanding things are left. Any chance we can quickly work on merging this? If you have no time to work on it, let me know and I'll continue the work myself. |
@austindrenski, I can see some activity on this PR, but it currently lists 104 commits... This is probably a result of a problematic merge. The best thing to do would be to take the current Npgsql dev branch as the new base for your PR branch, and cherry-pick your changes on top of it. The PR should show only the commits you've added to implement this functionality. Post back if this is confusing and you need git guidance. |
@roji, It looks like I missed your ping from back in March, apologies for that. As for all the commits... some git guidance on cleaning up the PR would be much appreciated. I seem to have gone a little awry while bringing my branch up to date... |
So the goal is to update your PR branch, First, I'd "back up" your current Then, I'd checkout the At this you can cherry-pick your commit where you did actual work by doing At this point you can update the PR on github simply by force-pushing your branch to github: Once everything is good, you can delete your older work via |
I appreciate the guidance, but I'm still having trouble resolving the conflicts on my end while going through the cherry-picking process. Is there any harm from to the PR process in rebasing and...manually recreating the changes prior to the force push? |
No harm in doing anything - whatever it is you end up doing, in the end you should have a clean local |
…ange operators with extension methods and provides hooks for consumers to include local evaulation implementations. Adds basic tests to verify SQL generation.
7661b77
to
4e1820d
Compare
Alright, I think the rebase and change incorporation went through okay. Changes since the last round of commits:
|
Thanks @austindrenski, I'll try to review this ASAP. In the meantime, I suggest you take a look at CustomBinaryExpression, which is perfect exactly for this kind of case (rather than having to define a new expression type just for range operations). It wasn't there when you originally started working, it would probably cut down on quite a bit of code. I'll do a proper review soon. |
I took a look at I'm wrapping up a refactor now. If you haven't started yet, I recommend waiting until these commits are pushed. |
…ew range operator methods for translation (e.g. client code can define and register a method that supports client evaluation). The goal is to build on CustomBinaryExpression and make it easier to add new operators (e.g. XML operators from issue #7). With the current structure, additional operator classes can be codified in 3 files: Npgsql{type_name}OperatorType, Npgsql{type_name}OperatorAttribute, and Npgsql{type_name}OperatorTranslator.
I finished the refactoring to use I tried to keep things extensible with
To support these hooks, I moved |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@austindrenski I reviewed your PR, thanks again for your work. Apart from the specific comments below, here are some more general ones.
I understand what you're trying to do with NpgsqlBinaryOperatorAttribute
, NpgsqlRangeOperatorAttribute
and the "dynamic" discovery of extension methods, but I honestly think it's a little over-engineered for this specific case:
- There's simply a great number of different classes to support this mechanism: a
NpgsqlRangeOperator
attribute, which accepts anNpgsqlRangeOperatorType
enum, which is annotated with anNpgsqlBinaryOperator
, with all this needing to be processed by the translator. - This sort of "generic" programming frequently creates issues such as this one, where you end up making assumptions which may not hold.
- A typical translator simply knows the methods it translates, and contains all the logic for translating them. This concentrates all the logic in a single, clear place, and allows you to handle different method calls in a special way if needed (and it's frequently needed in EF Core translators). A good example is the full text operation translator, where we have a simple switch that creates the CustomBinaryExpressions. You can also look at the NetTopologySuite translator (still WIP).
- Another problem I have with this is that it mixes different concerns together architecturally. The extension methods in NpgsqlRangeExtensions are pure .NET methods, which aren't supposed to be concerned with how they're rendered into SQL (in some cases they even contain a C# implementation). Putting
- Finally, the attribute-based mechanism is only relevant for extension methods added by the EF Core provider, but it also has to translate completely external methods - BCL methods, methods from other libraries (e.g. NetTopologySuite), etc. I think it's healthier to have one simple way to translate, rather than have a special attribute-based mechanism for Npgsql-defined extension methods, and another mechanism for the others.
On another note... I'm not sure I see the need for the extensibility features on NpgsqlRangeOperatorTranslator
- who exactly is going to be adding and removing methods via this API? If in the future we add any methods in NpgsqlRangeExtensions (or even elsewhere), at the same time we'd commit changes to the translator to recognize these methods. It's also worth mentioning that EF Core already provides a more general extensibility model by using dependency injection: if we want to translate other methods, we can do so by adding another translator to NpgsqlCompositeMethodCallTranslator. In fact, this is exactly what the new plugin model does in 2.1 (not yet merged but visible in https://github.com/npgsql/Npgsql.EntityFrameworkCore.PostgreSQL/tree/plugins).
To summarize, the work is very good - I'm just concerned with the factoring/architecture, which should probably be much simpler. I propose you look at some other translators, and if you still disagree we can definitely discuss.
PS The comprehensive test suite is really good, thanks for that.
/// <summary> | ||
/// Determines whether a range contains a specified value. | ||
/// </summary> | ||
/// <param name="range"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: here and below, why not put the <param>
and <typeparam>
in a single line:
/// <param name="range">The range in which to locate the value</param>
This seems to be standard in the .NET / EF Core code bases and reduces lots of useless lines
/// <value>true</value> if the range contains the specified value; otherwise, <value>false</value>. | ||
/// </returns> | ||
[NpgsqlRangeOperator(NpgsqlRangeOperatorType.Contains)] | ||
public static bool Contains<T>(this NpgsqlRange<T> range, T value) where T : IComparable<T> => throw new NotImplementedException(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should throw NotSupportException
, not NotImplementedException
.
.Where(x => !x.Range.Contains(range)) | ||
.ToArray(); | ||
|
||
Assert.Contains("WHERE NOT (x.\"Range\" @> @__range_0 = TRUE)", Fixture.TestSqlLoggerFactory.Sql); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Refactor this out to a single AssertContainsSql()
which can be called from all the tests.
/// By default, this class supports translation for methods declared on <see cref="NpgsqlRangeExtensions"/>. | ||
/// Additional methods can be supported so long as they declare a <see cref="NpgsqlRangeOperatorAttribute"/>. | ||
/// </remarks> | ||
public class NpgsqlRangeOperatorTranslator : IMethodCallTranslator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please rename to NpgsqlRangeTranslator to align with the naming scheme of other translators.
Check.NotNull(right, nameof(right)); | ||
|
||
// TODO: will the left type arguments always be valid? | ||
Type type = ReturnType.IsGenericType ? ReturnType.MakeGenericType(left.Type.GetGenericArguments()) : ReturnType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: please use var
everywhere
Check.NotNull(left, nameof(left)); | ||
Check.NotNull(right, nameof(right)); | ||
|
||
// TODO: will the left type arguments always be valid? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my general comment about the architecture. Simply moving all the logic into the translator makes this go away, as you'd be translating each operation separately and can make this decision on a per-operation basis.
@roji Thanks for the prompt review. I've pushed a commit addressing your comments, and it's ready for another round of review.
Those are very fair points. Perhaps too much time with ASP.NET Core has made every problem look well-suited for an attribute-based solution. Incorporating your comments eliminated a bunch of complexity and removed 3 extra files. Appreciate all the feedback! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@austindrenski thanks for the changes - this looks much better. This is really close for merging, see my last minor comments for further simplifying the code.
PS And thanks for taking my previous comments in a positive way. I have the same tendency for over-engineering things, I recognized myself there for a moment :)
/// The expression if successful; otherwise, null. | ||
/// </returns> | ||
[CanBeNull] | ||
static Expression TryTranslateOperator([NotNull] MethodCallExpression expression) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where not merge TryTranslateOperator
with Translate
above, to just have the entire switch in Translate()
? The separation doesn't seem to add any value. In addition, methods start with Try in C# normally return a bool (success/failure) and have an out
var, so this method isn't really following standard naming.
/// A <see cref="CustomBinaryExpression"/>. | ||
/// </returns> | ||
[NotNull] | ||
static Expression MakeBinaryExpression([NotNull] MethodCallExpression expression, [NotNull] string symbol, [NotNull] Type returnType) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the above, why not just instantiate CustomBinaryExpression
inside the switch, rather than calling this method which does it? There's almost zero value added here.
/// <summary> | ||
/// Represents a fixture suitable for testing range operators. | ||
/// </summary> | ||
public class RangeQueryNpgsqlFixture : IDisposable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In EF Core the standard now seems to be to include the fixture as an inner class of the test suite class. This makes sense since the fixture is only used within its test suite, and this way we have one file per test suite - easier to navigate the ever-growing list of test suites. Let's do this here as well.
@roji The I also relocated the fixture classes and removed that file. Given the size of these test suites, happy to see EF Core projects move that direction. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect, thanks for your contribution and your patience.
When a custom binary operator uses a standard comparison operator (=, <>, <, >, <=, >=) EF Core will generate a pattern that causes a syntax error in PostgreSQL. Example SQL: WHERE a < b = TRUE This patch adds logic to check if the custom binary operator is one of the standard comparison operators. If so, it wraps the expression in parenthesis. While it seems like this could be avoided by having the custom translator construct a standard Expression.LessThan(Expression,Expression), this causes an error to be thrown...because the binary operator isn't defined. See the related links for more information. Example stack trace: System.InvalidOperationException : The binary operator LessThanOrEqual is not defined for the types 'System.Net.IPAddress' and 'System.Net.IPAddress'. at System.Linq.Expressions.Expression.GetUserDefinedBinaryOperatorOrThrow(...) at System.Linq.Expressions.Expression.GetComparisonOperator(...) at System.Linq.Expressions.Expression.LessThanOrEqual(...) Related: - dotnet/efcore#9143 - npgsql#323 (review)
When a custom binary operator uses a standard comparison operator (`=`, `<>`, `<`, `>`, `<=`, `>=`) EF Core will generate a pattern that causes a syntax error in PostgreSQL. Example SQL: ```sql WHERE x."Inet" < @__inet_1 = TRUE ``` This patch adds logic to check if the custom binary operator is one of the standard comparison operators. If so, it wraps the expression in parenthesis. Example SQL (patched): ```sql WHERE (x."Inet" < @__inet_1) = TRUE ``` While it seems like this could be avoided by having the custom translator construct a standard Expression.LessThan(Expression,Expression), this causes an error to be thrown...because the binary operator isn't defined. Example stack trace: ``` System.InvalidOperationException : The binary operator LessThanOrEqual is not defined for the types 'System.Net.IPAddress' and 'System.Net.IPAddress'. at System.Linq.Expressions.Expression.GetUserDefinedBinaryOperatorOrThrow(...) at System.Linq.Expressions.Expression.GetComparisonOperator(...) at System.Linq.Expressions.Expression.LessThanOrEqual(...) ``` Related: - [](dotnet/efcore#9143) - [](npgsql#323 (review))
When a custom binary operator uses a standard comparison operator (`=`, `<>`, `<`, `>`, `<=`, `>=`) EF Core will generate a pattern that causes a syntax error in PostgreSQL. Example SQL: ```sql WHERE x."Inet" < @__inet_1 = TRUE ``` This patch adds logic to check if the custom binary operator is one of the standard comparison operators. If so, it wraps the expression in parenthesis. Example SQL (patched): ```sql WHERE (x."Inet" < @__inet_1) = TRUE ``` While it seems like this could be avoided by having the custom translator construct a standard `Expression.LessThan(Expression,Expression)`, this causes an error to be thrown...because the binary operator isn't defined. Example stack trace: ``` System.InvalidOperationException : The binary operator LessThanOrEqual is not defined for the types 'System.Net.IPAddress' and 'System.Net.IPAddress'. at System.Linq.Expressions.Expression.GetUserDefinedBinaryOperatorOrThrow(...) at System.Linq.Expressions.Expression.GetComparisonOperator(...) at System.Linq.Expressions.Expression.LessThanOrEqual(...) ``` Related: - dotnet/efcore#9143 - npgsql#323 (review)
When a custom binary operator uses a standard comparison operator (`=`, `<>`, `<`, `>`, `<=`, `>=`) EF Core will generate a pattern that causes a syntax error in PostgreSQL. Example SQL: ```sql WHERE x."Inet" < @__inet_1 = TRUE ``` This patch adds logic to check if the custom binary operator is one of the standard comparison operators. If so, it wraps the expression in parenthesis. Example SQL (patched): ```sql WHERE (x."Inet" < @__inet_1) = TRUE ``` While it seems like this could be avoided by having the custom translator construct a standard `Expression.LessThan(Expression,Expression)`, this causes an error to be thrown...because the binary operator isn't defined. Example stack trace: ``` System.InvalidOperationException : The binary operator LessThanOrEqual is not defined for the types 'System.Net.IPAddress' and 'System.Net.IPAddress'. at System.Linq.Expressions.Expression.GetUserDefinedBinaryOperatorOrThrow(...) at System.Linq.Expressions.Expression.GetComparisonOperator(...) at System.Linq.Expressions.Expression.LessThanOrEqual(...) ``` Related: - dotnet/efcore#9143 - #323 (review)
Map range operations for
NpgsqlRange<T>
Work in progress. See #311 for more information.
Implemented:
Adds extension methods with PostgreSQL translation:
Planned:
NpgsqlRange<T>
translate into their Postgres functions.range.LowerBound
->lower(range)
Issues:
range @> range
generates SQL likerange @> range = TRUE
NpgsqlRange<T>
should implementIComparable<T>
NpgsqlRange<T>
are returning unexpected results.