-
Notifications
You must be signed in to change notification settings - Fork 934
Fix for update with outer join in PostgreSQL #1568
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
Fix for update with outer join in PostgreSQL #1568
Conversation
9ff5f3a
to
d61cae6
Compare
Hi, please help me with the test failure on TeamCity. I'm not sure how to make it only runs with Postgres. |
I think your |
@Doan-Van-Tuan, @bahusoid is right. |
And also, please reformat the .cs test files added in this PR. There is a mix of tabs and spaces, NHibernate uses tabs. |
Thank you. I'm not familiar with NHibernate test configurations and conventions. |
|
||
protected override bool AppliesTo(Dialect.Dialect dialect) | ||
{ | ||
return dialect is PostgreSQLDialect; |
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.
This limitation should be removed. The lock should work for every database, better test them all. So remove the AppliesTo
override.
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.
AppliesTo() was removed.
@@ -181,9 +187,10 @@ public SqlSelectBuilder SetHavingClause(SqlString havingSqlString) | |||
return this; | |||
} | |||
|
|||
public SqlSelectBuilder SetLockMode(LockMode lockMode) | |||
public SqlSelectBuilder SetLockMode(LockMode lockMode, string alias) |
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.
This is a binary breaking change. Changing a public method must be done in two steps: obsolete the method, add an overload. The alias
parameter could be a bit more explicit about what it is, such as fromTableAlias
. Since the for update can accept a list of tables, it could even be something like nonOuterAliases
.
@@ -40,6 +42,7 @@ public SqlSelectBuilder SetComment(string comment) | |||
public SqlSelectBuilder SetFromClause(string fromClause) | |||
{ | |||
this.fromClause = fromClause; | |||
hasOuterJoin = hasOuterJoin || fromClause.ToLowerInvariant().Contains("left outer join"); |
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.
Better use this code for a case insensitive Contains
. It has been merge just now, so update your branch for having this StringHelper.ContainsCaseInsensitive
method.
The test should be moved into GetForUpdateString
, and done only if the dialect does not support for update on outer joins. It would avoid doing such a string scan for all other cases.
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.
Changed to StringHelper.ContainsCaseInsensitive
; and moved the check for outer join into GetForUpdateString()
.
@@ -110,6 +113,9 @@ public SqlSelectBuilder SetOuterJoins(SqlString outerJoinsAfterFrom, SqlString o | |||
} | |||
|
|||
this.outerJoinsAfterWhere = tmpOuterJoinsAfterWhere; | |||
|
|||
hasOuterJoin = hasOuterJoin || | |||
outerJoinsAfterFrom != null && outerJoinsAfterFrom.ToString().ToLowerInvariant().Contains("outer join"); |
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.
The test should be moved into GetForUpdateString
, and done only if the dialect does not support for update on outer joins. It would avoid doing such a string scan for all other cases.
By the way, is it really required here to test the outerJoinsAfterFrom
contains an outer join? Shouldn't it be enough to test the string is not null or just whitespaces?
if (!Dialect.SupportsOuterJoinForUpdate && hasOuterJoin) | ||
{ | ||
if (Equals(lockMode, LockMode.Upgrade)) | ||
return Dialect.GetForUpdateString(aliasToLock); |
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.
We still have a trouble here: databases not supporting for update
on outer join and not supporting for update
aliases. For such database the code here will keep a for update
clause, and it will still fail.
So either a new property needs to be added for knowing if the dialect supports lock aliases (currently, if it does not, it yields the for update
without aliases), and used here, or the for update should just yield here an empty string, removing the need for transmitting the alias by the 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.
Is there any DBMS that matches? I'd like to leave this logic for a later date when we need it.
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.
Maybe not, but see my update on #1565, it is definitely incorrect to systematically supply a table alias here. Some DB do take indeed a column list here, not a table alias. The correct code handling both cases is in ForUpdateFragment
.
…ining outer join if database (e.g Postgres) does not support locking with outer join
- Restore and mark SetLockMode(LockMode) obsolete - Only check for outer join if the dialect is not support locking entire outer join - Rename test class to reflect that it not limited to PostgreSQL
fc15b27
to
7effa35
Compare
{ | ||
return | ||
StringHelper.ContainsCaseInsensitive(fromClause, "outer join") || | ||
!string.IsNullOrWhiteSpace(outerJoinsAfterFrom?.ToString()); |
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.
It's better to swap those conditions and if empty/whitespace check is really required use SqlString.IsEmptyOrWhitespace
public bool IsEmptyOrWhitespace() |
outerJoinsAfterFrom?.IsEmptyOrWhitespace() == false
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.
@Doan-Van-Tuan could you please incorporate this suggestion?
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 resume work on this fix in next few days. It was our Lunar New Year.
@fredericDelaporte could you please review this to include in 5.1. It seems only minor request is missing. I'm going to adjust it myself. |
d95a025
to
133f244
Compare
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.
Approving up to Remove undue whitespace addition and do condition swap recommended by bahusoid
commit.
The other commits I have added should be approved by another reviewer I think.
src/NHibernate/Dialect/Dialect.cs
Outdated
|
||
/// <summary>Is <c>FOR UPDATE OF</c> syntax expecting columns?</summary> | ||
/// <value><see langword="true"/> if the database expects a column list with <c>FOR UPDATE OF</c> syntax, | ||
/// <see langword="false"/> if it expects table alias instead or do not support <c>FOR UPDATE OF</c> syntax.</value> | ||
public virtual bool ForUpdateOfColumns |
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.
Its previous comment was wrong. It is actually used to determine whether columns aliases (true value) or tables aliases (false value) have to be supplied to GetForUpdateString(string aliases)
. Then GetForUpdateString(string aliases)
implementation will ignore supplied aliases if the dialect is not supporting for update of
at all.
It still does that without relying on the new ForUpdateOf
I have added.
{ | ||
var isUpgrade = Equals(lockMode, LockMode.Upgrade); | ||
var isUpgradeNoWait = !isUpgrade && ( | ||
Equals(lockMode, LockMode.UpgradeNoWait) || Equals(lockMode, LockMode.Force)); |
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.
Adding forgotten Force
case by the way. Previously this case would have fallback on Dialect.GetForUpdateString(LockMode)
which would then have emit an unsupported for update
clause.
var isUpgradeNoWait = !isUpgrade && ( | ||
Equals(lockMode, LockMode.UpgradeNoWait) || Equals(lockMode, LockMode.Force)); | ||
if (!isUpgrade && !isUpgradeNoWait) | ||
return string.Empty; |
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.
Added shortcut. Previous code would have call Dialect.GetForUpdateString(lockMode)
instead, but now, with Force
handled, it should only yield string.Empty
.
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.
Thank you all for picking up and finish the work on this. I'm on a tight schedule and could not find the peace of mind to finalize it.
src/NHibernate/Dialect/Dialect.cs
Outdated
/// <value> True if the database supports <tt>FOR UPDATE OF</tt> syntax; false otherwise. </value> | ||
/// <summary>Is <c>FOR UPDATE OF</c> syntax supported?</summary> | ||
/// <value><see langword="true"/> if the database supports <c>FOR UPDATE OF</c> syntax; <see langword="false"/> otherwise. </value> | ||
public virtual bool ForUpdateOf |
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.
SupportsForUpdateOf
src/NHibernate/Dialect/Dialect.cs
Outdated
|
||
/// <summary>Is <c>FOR UPDATE OF</c> syntax expecting columns?</summary> | ||
/// <value><see langword="true"/> if the database expects a column list with <c>FOR UPDATE OF</c> syntax, | ||
/// <see langword="false"/> if it expects table alias instead or do not support <c>FOR UPDATE OF</c> syntax.</value> | ||
public virtual bool ForUpdateOfColumns |
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.
true
/false
semantic does not make sense, at least with the current name of the property. Let's rename it to RequiresColumnsForUpdateOf
, or similar if you know a better name.
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.
UsesColumnsWithForUpdateOf
?
… too). Renaming an existing property without causing a breaking change causes some bloat.
Add alias in "for update" clause to entity loading query containing outer join if database (e.g Postgres) does not support locking with outer join.
Fixes #1565.