Skip to content

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

Conversation

Doan-Van-Tuan
Copy link
Contributor

@Doan-Van-Tuan Doan-Van-Tuan commented Feb 1, 2018

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.

@Doan-Van-Tuan Doan-Van-Tuan force-pushed the bugfix/1565-postgres-outer-join-entity-lock-issue branch from 9ff5f3a to d61cae6 Compare February 1, 2018 07:38
@Doan-Van-Tuan Doan-Van-Tuan reopened this Feb 1, 2018
@Doan-Van-Tuan
Copy link
Contributor Author

Hi, please help me with the test failure on TeamCity. I'm not sure how to make it only runs with Postgres.

@bahusoid
Copy link
Member

bahusoid commented Feb 1, 2018

I think your Configure override needs to be removed. It seems it forces Postgres dialect on all configurations.

@hazzik
Copy link
Member

hazzik commented Feb 1, 2018

@Doan-Van-Tuan, @bahusoid is right.

@fredericDelaporte
Copy link
Member

And also, please reformat the .cs test files added in this PR. There is a mix of tabs and spaces, NHibernate uses tabs.

@Doan-Van-Tuan
Copy link
Contributor Author

Thank you. I'm not familiar with NHibernate test configurations and conventions.

@hazzik hazzik added the t: Fix label Feb 1, 2018

protected override bool AppliesTo(Dialect.Dialect dialect)
{
return dialect is PostgreSQLDialect;
Copy link
Member

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.

Copy link
Contributor Author

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

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

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.

Copy link
Contributor Author

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

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?

@fredericDelaporte fredericDelaporte changed the title GH1565 Add alias in "for update" clause to entity loading query conta… For update with outer join fails with PostgreSQL Feb 1, 2018
if (!Dialect.SupportsOuterJoinForUpdate && hasOuterJoin)
{
if (Equals(lockMode, LockMode.Upgrade))
return Dialect.GetForUpdateString(aliasToLock);
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Doan Van Tuan added 4 commits February 2, 2018 13:41
…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
@Doan-Van-Tuan Doan-Van-Tuan force-pushed the bugfix/1565-postgres-outer-join-entity-lock-issue branch from fc15b27 to 7effa35 Compare February 3, 2018 03:37
{
return
StringHelper.ContainsCaseInsensitive(fromClause, "outer join") ||
!string.IsNullOrWhiteSpace(outerJoinsAfterFrom?.ToString());
Copy link
Member

@bahusoid bahusoid Feb 3, 2018

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

Copy link
Member

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?

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 resume work on this fix in next few days. It was our Lunar New Year.

@hazzik hazzik changed the title For update with outer join fails with PostgreSQL WIP For update with outer join fails with PostgreSQL Feb 28, 2018
@hazzik
Copy link
Member

hazzik commented Mar 12, 2018

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

@hazzik hazzik changed the title WIP For update with outer join fails with PostgreSQL Fix for update with outer join in PostgreSQL Mar 13, 2018
hazzik
hazzik previously approved these changes Mar 13, 2018
@fredericDelaporte fredericDelaporte force-pushed the bugfix/1565-postgres-outer-join-entity-lock-issue branch from d95a025 to 133f244 Compare March 13, 2018 11:59
Copy link
Member

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


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

@fredericDelaporte fredericDelaporte Mar 13, 2018

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

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

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.

Copy link
Contributor Author

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.

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SupportsForUpdateOf


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

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.

Copy link
Member

@fredericDelaporte fredericDelaporte Mar 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UsesColumnsWithForUpdateOf?

@fredericDelaporte fredericDelaporte added this to the 5.1 milestone Mar 14, 2018
@fredericDelaporte fredericDelaporte merged commit e38566d into nhibernate:master Mar 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants