Skip to content

Conversation

@austindrenski
Copy link
Contributor

@austindrenski austindrenski commented Jun 23, 2018

Copy link
Member

@roji roji left a 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,
Copy link
Member

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?

Copy link
Contributor Author

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
Copy link
Member

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; }
Copy link
Member

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; }
Copy link
Member

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 =
Copy link
Member

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].

Copy link
Contributor Author

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");
Copy link
Member

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)
Copy link
Member

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.

Copy link
Contributor Author

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)?

Copy link
Member

@roji roji Jul 15, 2018

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))
Copy link
Member

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;
Copy link
Member

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...

Copy link
Contributor Author

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.

@austindrenski austindrenski force-pushed the compatibility branch 3 times, most recently from d401c30 to 0e6ac66 Compare July 14, 2018 20:27
@austindrenski austindrenski changed the title Add support for compatibility versioning Add support for backend type and version compatibility Jul 14, 2018
@austindrenski austindrenski changed the title Add support for backend type and version compatibility Add support for backend compatibility Jul 14, 2018
@austindrenski
Copy link
Contributor Author

@roji Thanks for the review! I've pushed a commit to address your 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.

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.

/// </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)
Copy link
Member

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;
Copy link
Member

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;
Copy link
Member

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)
Copy link
Member

@roji roji Jul 15, 2018

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?

Copy link
Member

@roji roji left a 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.

@austindrenski
Copy link
Contributor Author

@roji wrote:

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.

@austindrenski wrote:

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)?

@roji wrote:

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?

I think that's the best way forward. BackendType can always be reintroduced if/when we have more clarity.

I've removed BackendType in the latest commit, and renamed BackendVersion to PostgresVersion.

@austindrenski austindrenski force-pushed the compatibility branch 4 times, most recently from 0e02130 to 416ba8b Compare July 15, 2018 20:59
Copy link
Member

@roji roji left a 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);
Copy link
Member

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:

  1. 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.
  2. This value won't evolve unless we make it evolve with each and every PostgreSQL release.
  3. 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.

Copy link
Contributor Author

@austindrenski austindrenski Jul 17, 2018

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.

Copy link
Contributor Author

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) ||
Copy link
Member

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

Copy link
Contributor Author

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.

AddTranslators(instanceTranslators);

foreach (var plugin in npgsqlOptions.Plugins)
{
Copy link
Member

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.

Copy link
Contributor Author

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.)

@roji
Copy link
Member

roji commented Jul 22, 2018

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.
- 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`.
This will help differentiate user-defined versions from defaults.
Also allows other options to be null by default.
@austindrenski austindrenski merged commit 04ce9cd into npgsql:dev Jul 22, 2018
@austindrenski austindrenski deleted the compatibility branch July 22, 2018 13:27
roji pushed a commit that referenced this pull request Jul 22, 2018
@roji
Copy link
Member

roji commented Jul 22, 2018

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 git rebase -i to clean it up, but you'll likely have to rewind your branch (git reset --hard to before this PR was merged) and pull again. If you have any issues let me know.

In general I try to avoid rewriting history because of the mess it creates but sometimes it makes sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants