-
Notifications
You must be signed in to change notification settings - Fork 255
Add support for backend compatibility #483
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
Conversation
acf70b8 to
590ec36
Compare
roji
left a comment
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 @austindrenski, looks good, see my comments.
As a general note, just to help me review, in the future try to do unrelated documentation/refactoring/cleanup in separate PRs... I know as you're working on your PR you see issues and it's hard to resist, but it's really much easier to review a minimal, tight PR without any unrelated changes. You can always submit another PR afterwards that only does docs/cosmetics, it's very easy to review that.
| /// <summary> | ||
| /// The backend process is PostgreSQL. | ||
| /// </summary> | ||
| PostgreSQL = 0, |
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/curiosity: any reason to assign the 0 here? And not to assign other values?
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.
No real reason right now, I can omit that in the next commit.
It was there initially for some earlier testing where the backend type was marked with [Flags]. But it made the condition testing look a little complicated, so I rolled it back to a simple enum for the first iteration.
| /// Describes the backend process that uses the PostgreSQL wire protocol. | ||
| /// </summary> | ||
| [PublicAPI] | ||
| public enum Backend |
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 calling this BackendType is more clear.
| /// Reflects the option set by <see cref="NpgsqlDbContextOptionsBuilder.ReverseNullOrdering" />. | ||
| /// The backend process to target. | ||
| /// </summary> | ||
| Backend Backend { get; } |
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.
As above, BackendType seems better.
| /// The backend version to target. | ||
| /// </summary> | ||
| [NotNull] | ||
| public Version Compatibility { get; private set; } |
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.
How about just calling this BackendVersion? Compatibility is pretty vague and doesn't actually describe the value...
| /// <summary> | ||
| /// The default method call translators registered by the Npgsql provider. | ||
| /// </summary> | ||
| [NotNull] [ItemNotNull] static readonly IMethodCallTranslator[] MethodCallTranslators = |
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.
One day C# 8 will come out with non-nullable references and this attribute clutter will all go away... :)
Note that R#/Rider aren't amazing at enforcing these (especially the item annotations) which is one reason I don't put them very much. There's also a R# extension that treats references as non-nullable by default (unless you specify [CanBeNull]), which at least gets rid of all the [NotNull].
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.
Very much looking forward to that day. Clarifying nullability on what are effectively runtime constants is......tiring. I like the sounds of that R# extension, I'll give it a look.
| /// <summary> | ||
| /// The minimum backend version supported by this translator. | ||
| /// </summary> | ||
| [NotNull] static readonly Version MinimumSupportedVersion = new Version("9.4"); |
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: initialize with ints instead of a string...
| { | ||
| if (!_methodInfoDatePartMapping.TryGetValue(methodCallExpression.Method, out var datePart)) | ||
| // This translation is only supported for PostgreSQL 9.4 or higher. | ||
| if (_backend != Backend.PostgreSQL || _compatibility < MinimumSupportedVersion) |
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 requires more thought. It's true that this won't be supported on Redshift, but that's simply because it forked from PostgreSQL 8.0.x. On the other hand CrateDB and CockroachDB may actually support it.
I think that in as a general rule looking at the backend version should be enough for most cases, and looking at its type is unnecessary/confusing.
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'll remove the backend type check for now. But this raises the question of which version do we mean for BackendVersion to represent?
Should it be the actual backend's version (e.g. type: Redshift, version: Redshift-X.X) or the equivalent PostgreSQL version (e.g. type: Redshift, version: PostgreSQL-9.4)?
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.
That's a good question, I can see arguments both ways... I'm not sure we have enough actual information on this.
Redshift forked from PostgreSQL around version 8.0.2, and has not progressed in terms of "PostgreSQL versions" - AFAIK there are no post-8.0.2 PostgreSQL language features that have been ported over to Redshift. But in any case running EF Core over Redshift may very well be much too much effort, and not necessarily a great fit. I wouldn't worry about it too much.
For the others (CockroachDB, CrateDB) I simply don't know... In the absence of information it may be safer to simply have a PostgresVersion (rather than BackendVersion, less ambiguous), and possibly have the optional BackendType determine a PostgresVersion... So that if BackendType is set to be Redshift, we'd automatically set PostgresVersion to be 8.0.2 (in addition to making some other, truly Redshift-specific behavior modifications).
But this is really largely theoretical at this point. It may make sense to remove BackendType altogether, and keep only PostgresVersion at this point, until we have an actual use case. What do you think?
| // ugly, so we just generate the expression ourselves with CustomBinaryExpression | ||
|
|
||
| var amountToAdd = methodCallExpression.Arguments.First(); | ||
| if (!(methodCallExpression.Object is Expression instance)) |
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.
Why not combine these in a single if?
| /// <summary> | ||
| /// The backend process to target. | ||
| /// </summary> | ||
| readonly Backend _backend; |
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 do you see this class needing to do compatibility checks, do you have a concrete compatibility issue in mind? If not I'd rather leave it out until we do...
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 had something like npgsql/npgsql#2023 in mind, but looking at it again, that doesn't really apply here. I'll roll these back until there's a reason to use them.
d401c30 to
0e6ac66
Compare
0e6ac66 to
0a418f8
Compare
|
@roji Thanks for the review! I've pushed a commit to address your comments.
That's my bad... It's hard to recognize mission creep in the moment. I rolled back some of the changes, and split others off into a separate PR, which brings the affected file count from 13 to 8. I'll try to keep an eye on the peripheral changes going forward. |
0a418f8 to
663f384
Compare
| /// </summary> | ||
| /// <param name="backendType">The backend process to target.</param> | ||
| /// <param name="compatibility">The backend version to target.</param> | ||
| public NpgsqlDateAddTranslator(BackendType backendType, [NotNull] Version compatibility) |
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.
As above, rename to backendVersion.
| /// <returns> | ||
| /// A SQL expression representing the translated MethodCallExpression. | ||
| /// </returns> | ||
| [NotNull] readonly Version _compatibility; |
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.
Still need to rename _compatibility here to _backendVersion.
| /// Translates the given method call expression. | ||
| /// The backend process to target. | ||
| /// </summary> | ||
| readonly BackendType _backendType; |
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.
As this isn't being used, it can be removed, right?
| { | ||
| if (!_methodInfoDatePartMapping.TryGetValue(methodCallExpression.Method, out var datePart)) | ||
| // This translation is only supported for PostgreSQL 9.4 or higher. | ||
| if (_backend != Backend.PostgreSQL || _compatibility < MinimumSupportedVersion) |
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.
That's a good question, I can see arguments both ways... I'm not sure we have enough actual information on this.
Redshift forked from PostgreSQL around version 8.0.2, and has not progressed in terms of "PostgreSQL versions" - AFAIK there are no post-8.0.2 PostgreSQL language features that have been ported over to Redshift. But in any case running EF Core over Redshift may very well be much too much effort, and not necessarily a great fit. I wouldn't worry about it too much.
For the others (CockroachDB, CrateDB) I simply don't know... In the absence of information it may be safer to simply have a PostgresVersion (rather than BackendVersion, less ambiguous), and possibly have the optional BackendType determine a PostgresVersion... So that if BackendType is set to be Redshift, we'd automatically set PostgresVersion to be 8.0.2 (in addition to making some other, truly Redshift-specific behavior modifications).
But this is really largely theoretical at this point. It may make sense to remove BackendType altogether, and keep only PostgresVersion at this point, until we have an actual use case. What do you think?
roji
left a comment
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.
Take a look at my new comments, I'd consider getting rid of BackendType altogether until we know exactly what we want/need, and simply have a PostgresVersion.
1e88fb6 to
ca4ebba
Compare
|
@roji wrote:
@austindrenski wrote:
@roji wrote:
I think that's the best way forward. I've removed |
0e02130 to
416ba8b
Compare
roji
left a comment
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.
Almost there, I think... Check out my comments.
| public NpgsqlOptionsExtension() | ||
| { | ||
| AdminDatabase = "postgres"; | ||
| PostgresVersion = new Version(10, 4); |
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'm not sure it's a good idea to hardcode a default version like this:
- It makes it impossible to distinguish between user-provided values and defaults - a distinction that may be important. With this change, if we ask the question "what is the current database compatibility version", the answer will be "10.4" even if the user hasn't specified anything.
- This value won't evolve unless we make it evolve with each and every PostgreSQL release.
- Finally, I'd rather have the default admin database (
postgres) specified in NpgsqlRelationalConnection
It seems safer and cleaner to just leave the options null if the user hasn't said anything. For PostgresVersion, a null would mean "assume the latest supported PostgreSQL version", i.e. don't turn off any features for backwards compat.
On the other hand, for ReverseNullOrdering I think your change is good - a nullable bool doesn't really help anyone and just makes things more complex.
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.
That makes sense. But, I'm going to punt on making any changes to the admin database stuff (since it was here before this PR)... I can take a look at changing it in a follow up PR.
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.
Actually, scratch that. I thought that was a default from before I started this PR, but the diff looks like I've added it.... Dropping the hard-coded admin default.
| // ugly, so we just generate the expression ourselves with CustomBinaryExpression | ||
|
|
||
| var amountToAdd = methodCallExpression.Arguments.First(); | ||
| if (!(methodCallExpression.Object is Expression instance) || |
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 like the new pattern matching syntax, but I'm not sure I like it as much as you :)
Unless I'm mistaken, if we're here, we already know that methodCallExpression.Object is non-null (thanks to the lookup above), that its type is Expression, and that there's one argument. So the conditions here are completely redundant, and seem to be used only to define new local variables, no?
If that's the case, I'd rather just define local variables. This makes the code easier to reason about, and doesn't make us doubt that maybe there's a chance of a null being there, etc...
I'm not sure I like this form of programming: the type of methodCallExpression.Object is Expression, and unless
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 am a bit enamoured with the pattern matching...but you're right that this one is a bit over the top.
However, I think we still want a (simple) null check on both the instance and the argument. While DateTime and DateTimeOffset are structs, it's always possible that an earlier translator/visitor may have have forwarded a null reference.
0f605b3 to
e297515
Compare
e297515 to
98adeed
Compare
| AddTranslators(instanceTranslators); | ||
|
|
||
| foreach (var plugin in npgsqlOptions.Plugins) | ||
| { |
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 actually prefer leaving out curlies for simple one-liners, but I'm not die-hard on that.
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 removed this in the rebase. (I've omitted them elsewhere to match the style...not sure why I added this one.)
|
Looks good, but I think the unrelated changes in this PR were submitted as other PRs, right? In which case they need to be removed from this one before merging... |
- Defaults compatibility version to 10.4. - Cleans up related files. - Adds (basic) documentation. - Adds nullability attributing where inconsistent.
- Also adds documentation throughout.
- Fixes issue 480.
- Defaults to PostgreSQL.
- Backend -> BackendType - Compatibiltiy -> BackendVersion - Rollback some peripheral changes - Add license header when missing in modified files
It may be useful, but until we have more insight into how PostgreSQL-like backends conform/differ, its safer to just focus on the equivalent PostgreSQL version identifier. Also renamed `BackendVersion` to `PostgresVersion`.
98adeed to
b6b4638
Compare
This will help differentiate user-defined versions from defaults. Also allows other options to be null by default.
b6b4638 to
bf756ae
Compare
|
Thanks for the work! Just a small note: you squashed all the commits in this PR (which is a good thing), but left all the joined commit messages in the final squashed commit, leaving a huge message with lots of irrelevant messages. It's no big deal - I rewrote the history with In general I try to avoid rewriting history because of the mess it creates but sometimes it makes sense. |
Related: