Skip to content

Commit d61cae6

Browse files
author
Doan Van Tuan
committed
GH1565 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
1 parent c90b08c commit d61cae6

File tree

5 files changed

+107
-3
lines changed

5 files changed

+107
-3
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernate.Test"
3+
namespace="NHibernate.Test.NHSpecificTest.GH1565">
4+
5+
<class name="MainEntity">
6+
<id name="Id" unsaved-value="0">
7+
<generator class="native" />
8+
</id>
9+
10+
<join table="EntityData" inverse="true">
11+
<key column="EntityId"/>
12+
<property name="Data" />
13+
</join>
14+
</class>
15+
</hibernate-mapping>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using NHibernate.Cfg;
2+
using NHibernate.Dialect;
3+
using NUnit.Framework;
4+
5+
namespace NHibernate.Test.NHSpecificTest.GH1565
6+
{
7+
[TestFixture]
8+
public class PostgresOuterJoinLockTest : BugTestCase
9+
{
10+
[Test]
11+
public void LockWithOuterJoin_ShouldBePossible()
12+
{
13+
using (var session = OpenSession())
14+
{
15+
using (var transaction = session.BeginTransaction())
16+
{
17+
var entity = session.Get<MainEntity>(id, LockMode.Upgrade);
18+
Assert.That(entity.Id, Is.EqualTo(id));
19+
transaction.Rollback();
20+
}
21+
}
22+
}
23+
24+
private int id;
25+
protected override void OnSetUp()
26+
{
27+
base.OnSetUp();
28+
using (var session = OpenSession())
29+
{
30+
using (var transaction = session.BeginTransaction())
31+
{
32+
session.FlushMode = FlushMode.Auto;
33+
var entity = new MainEntity();
34+
session.Save(entity);
35+
transaction.Commit();
36+
id = entity.Id;
37+
}
38+
}
39+
}
40+
41+
protected override void OnTearDown()
42+
{
43+
base.OnTearDown();
44+
using (var session = OpenSession())
45+
{
46+
session.CreateSQLQuery("delete from MainEntity").ExecuteUpdate();
47+
}
48+
}
49+
50+
protected override bool AppliesTo(Dialect.Dialect dialect)
51+
{
52+
return dialect is PostgreSQLDialect;
53+
}
54+
55+
protected override void Configure(Configuration configuration)
56+
{
57+
configuration.SetProperty(Cfg.Environment.Dialect, typeof(PostgreSQL83Dialect).FullName);
58+
configuration.SetProperty(Cfg.Environment.ConnectionDriver, typeof(Driver.NpgsqlDriver).FullName);
59+
}
60+
}
61+
62+
public class MainEntity
63+
{
64+
public virtual int Id { get; set; } = 0;
65+
66+
public virtual string Data { get; set; }
67+
}
68+
}

src/NHibernate/Dialect/PostgreSQLDialect.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ public override string CurrentTimestampSelectString
316316

317317
public override bool SupportsUnboundedLobLocatorMaterialization => false;
318318

319+
public override bool SupportsOuterJoinForUpdate => false;
319320
#endregion
320321

321322
[Serializable]

src/NHibernate/Loader/AbstractEntityJoinWalker.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ private void InitStatementString(SqlString projection, SqlString condition, SqlS
118118
JoinFragment ojf = MergeOuterJoins(associations);
119119

120120
SqlSelectBuilder select = new SqlSelectBuilder(Factory)
121-
.SetLockMode(lockMode)
121+
.SetLockMode(lockMode, alias)
122122
.SetSelectClause(selectClause)
123123
.SetFromClause(Dialect.AppendLockHint(lockMode, persister.FromTableFragment(alias)) +persister.FromJoinFragment(alias, true, true))
124124
.SetWhereClause(condition)

src/NHibernate/SqlCommand/SqlSelectBuilder.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ public class SqlSelectBuilder : SqlBaseBuilder, ISqlStringBuilder
2121
private SqlString groupByClause;
2222
private SqlString havingClause;
2323
private LockMode lockMode;
24+
private string aliasToLock;
2425
private string comment;
26+
private bool hasOuterJoin = false;
2527

2628
public SqlSelectBuilder(ISessionFactoryImplementor factory)
2729
: base(factory.Dialect, factory) {}
@@ -40,6 +42,7 @@ public SqlSelectBuilder SetComment(string comment)
4042
public SqlSelectBuilder SetFromClause(string fromClause)
4143
{
4244
this.fromClause = fromClause;
45+
hasOuterJoin = hasOuterJoin || fromClause.ToLowerInvariant().Contains("left outer join");
4346
return this;
4447
}
4548

@@ -110,6 +113,9 @@ public SqlSelectBuilder SetOuterJoins(SqlString outerJoinsAfterFrom, SqlString o
110113
}
111114

112115
this.outerJoinsAfterWhere = tmpOuterJoinsAfterWhere;
116+
117+
hasOuterJoin = hasOuterJoin ||
118+
outerJoinsAfterFrom != null && outerJoinsAfterFrom.ToString().ToLowerInvariant().Contains("outer join");
113119
return this;
114120
}
115121

@@ -181,9 +187,10 @@ public SqlSelectBuilder SetHavingClause(SqlString havingSqlString)
181187
return this;
182188
}
183189

184-
public SqlSelectBuilder SetLockMode(LockMode lockMode)
190+
public SqlSelectBuilder SetLockMode(LockMode lockMode, string alias)
185191
{
186192
this.lockMode = lockMode;
193+
aliasToLock = alias;
187194
return this;
188195
}
189196

@@ -267,7 +274,7 @@ public SqlString ToSqlString()
267274

268275
if (lockMode != null)
269276
{
270-
sqlBuilder.Add(Dialect.GetForUpdateString(lockMode));
277+
sqlBuilder.Add(GetForUpdateString());
271278
}
272279

273280
if (log.IsDebugEnabled())
@@ -291,6 +298,19 @@ public SqlString ToSqlString()
291298
return sqlBuilder.ToSqlString();
292299
}
293300

301+
private string GetForUpdateString()
302+
{
303+
if (!Dialect.SupportsOuterJoinForUpdate && hasOuterJoin)
304+
{
305+
if (Equals(lockMode, LockMode.Upgrade))
306+
return Dialect.GetForUpdateString(aliasToLock);
307+
if (Equals(lockMode, LockMode.UpgradeNoWait))
308+
return Dialect.GetForUpdateNowaitString(aliasToLock);
309+
}
310+
311+
return Dialect.GetForUpdateString(lockMode);
312+
}
313+
294314
#endregion
295315
}
296316
}

0 commit comments

Comments
 (0)