Skip to content

Commit 9d4b8c8

Browse files
authored
Fix possible ERROR log when parsing join in hql (#2859)
Fixes #2855
1 parent e5fcd13 commit 9d4b8c8

File tree

7 files changed

+128
-32
lines changed

7 files changed

+128
-32
lines changed

src/NHibernate.Test/Associations/OneToOneFixture.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using NHibernate.Cfg.MappingSchema;
44
using NHibernate.Mapping.ByCode;
55
using NHibernate.Test.Associations.OneToOneFixtureEntities;
6+
using NHibernate.Util;
67
using NUnit.Framework;
78

89
namespace NHibernate.Test.Associations
@@ -127,19 +128,22 @@ public void OneToOneCompositeQueryOverCompareWithJoinById()
127128
Assert.That(loadedEntity, Is.Not.Null);
128129
}
129130
}
130-
131+
131132
//GH-2064
132133
[Test]
133134
public void OneToOneCompositeQuerySelectProjection()
134135
{
136+
using(var logSpy = new LogSpy(typeof(ReflectHelper)))
135137
using (var session = OpenSession())
136138
{
137139
var loadedProjection = session.Query<Parent>().Select(x => new {x.OneToOneComp, x.Key}).FirstOrDefault();
138140

139141
Assert.That(loadedProjection.OneToOneComp, Is.Not.Null);
142+
// GH-2855 Error is logged
143+
Assert.That(logSpy.GetWholeLog(), Is.Empty);
140144
}
141145
}
142-
146+
143147
//NH-3178 (GH-1125)
144148
[Test]
145149
public void OneToOneQueryOverSelectProjection()

src/NHibernate.Test/Async/Associations/OneToOneFixture.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using NHibernate.Cfg.MappingSchema;
1414
using NHibernate.Mapping.ByCode;
1515
using NHibernate.Test.Associations.OneToOneFixtureEntities;
16+
using NHibernate.Util;
1617
using NUnit.Framework;
1718
using NHibernate.Linq;
1819

@@ -139,19 +140,22 @@ public async Task OneToOneCompositeQueryOverCompareWithJoinByIdAsync()
139140
Assert.That(loadedEntity, Is.Not.Null);
140141
}
141142
}
142-
143+
143144
//GH-2064
144145
[Test]
145146
public async Task OneToOneCompositeQuerySelectProjectionAsync()
146147
{
148+
using(var logSpy = new LogSpy(typeof(ReflectHelper)))
147149
using (var session = OpenSession())
148150
{
149151
var loadedProjection = await (session.Query<Parent>().Select(x => new {x.OneToOneComp, x.Key}).FirstOrDefaultAsync());
150152

151153
Assert.That(loadedProjection.OneToOneComp, Is.Not.Null);
154+
// GH-2855 Error is logged
155+
Assert.That(logSpy.GetWholeLog(), Is.Empty);
152156
}
153157
}
154-
158+
155159
//NH-3178 (GH-1125)
156160
[Test]
157161
public async Task OneToOneQueryOverSelectProjectionAsync()

src/NHibernate.Test/Async/Hql/EntityJoinHqlTest.cs

+41-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ namespace NHibernate.Test.Hql
2626
[TestFixture]
2727
public class EntityJoinHqlTestAsync : TestCaseMappingByCode
2828
{
29-
private const string _customEntityName = "CustomEntityName";
29+
private const string _customEntityName = "CustomEntityName.Test";
3030
private EntityWithCompositeId _entityWithCompositeId;
3131
private EntityWithNoAssociation _noAssociation;
3232
private EntityCustomEntityName _entityWithCustomEntityName;
@@ -51,6 +51,46 @@ public async Task CanJoinNotAssociatedEntityAsync()
5151
}
5252
}
5353

54+
[Test]
55+
public async Task CanJoinNotAssociatedEntityFullNameAsync()
56+
{
57+
using (var sqlLog = new SqlLogSpy())
58+
using (var session = OpenSession())
59+
{
60+
EntityComplex entityComplex =
61+
await (session
62+
.CreateQuery("select ex " +
63+
"from EntityWithNoAssociation root " +
64+
$"left join {typeof(EntityComplex).FullName} ex with root.Complex1Id = ex.Id")
65+
.SetMaxResults(1)
66+
.UniqueResultAsync<EntityComplex>());
67+
68+
Assert.That(entityComplex, Is.Not.Null);
69+
Assert.That(NHibernateUtil.IsInitialized(entityComplex), Is.True);
70+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
71+
}
72+
}
73+
74+
[Test]
75+
public async Task CanJoinNotAssociatedInterfaceFullNameAsync()
76+
{
77+
using (var sqlLog = new SqlLogSpy())
78+
using (var session = OpenSession())
79+
{
80+
EntityComplex entityComplex =
81+
await (session
82+
.CreateQuery("select ex " +
83+
"from EntityWithNoAssociation root " +
84+
$"left join {typeof(IEntityComplex).FullName} ex with root.Complex1Id = ex.Id")
85+
.SetMaxResults(1)
86+
.UniqueResultAsync<EntityComplex>());
87+
88+
Assert.That(entityComplex, Is.Not.Null);
89+
Assert.That(NHibernateUtil.IsInitialized(entityComplex), Is.True);
90+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
91+
}
92+
}
93+
5494
[Test]
5595
public async Task CanJoinNotAssociatedEntity_OnKeywordAsync()
5696
{

src/NHibernate.Test/Hql/EntityJoinHqlTest.cs

+41-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace NHibernate.Test.Hql
1414
[TestFixture]
1515
public class EntityJoinHqlTest : TestCaseMappingByCode
1616
{
17-
private const string _customEntityName = "CustomEntityName";
17+
private const string _customEntityName = "CustomEntityName.Test";
1818
private EntityWithCompositeId _entityWithCompositeId;
1919
private EntityWithNoAssociation _noAssociation;
2020
private EntityCustomEntityName _entityWithCustomEntityName;
@@ -39,6 +39,46 @@ public void CanJoinNotAssociatedEntity()
3939
}
4040
}
4141

42+
[Test]
43+
public void CanJoinNotAssociatedEntityFullName()
44+
{
45+
using (var sqlLog = new SqlLogSpy())
46+
using (var session = OpenSession())
47+
{
48+
EntityComplex entityComplex =
49+
session
50+
.CreateQuery("select ex " +
51+
"from EntityWithNoAssociation root " +
52+
$"left join {typeof(EntityComplex).FullName} ex with root.Complex1Id = ex.Id")
53+
.SetMaxResults(1)
54+
.UniqueResult<EntityComplex>();
55+
56+
Assert.That(entityComplex, Is.Not.Null);
57+
Assert.That(NHibernateUtil.IsInitialized(entityComplex), Is.True);
58+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
59+
}
60+
}
61+
62+
[Test]
63+
public void CanJoinNotAssociatedInterfaceFullName()
64+
{
65+
using (var sqlLog = new SqlLogSpy())
66+
using (var session = OpenSession())
67+
{
68+
EntityComplex entityComplex =
69+
session
70+
.CreateQuery("select ex " +
71+
"from EntityWithNoAssociation root " +
72+
$"left join {typeof(IEntityComplex).FullName} ex with root.Complex1Id = ex.Id")
73+
.SetMaxResults(1)
74+
.UniqueResult<EntityComplex>();
75+
76+
Assert.That(entityComplex, Is.Not.Null);
77+
Assert.That(NHibernateUtil.IsInitialized(entityComplex), Is.True);
78+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
79+
}
80+
}
81+
4282
[Test]
4383
public void CanJoinNotAssociatedEntity_OnKeyword()
4484
{

src/NHibernate.Test/Hql/EntityJoinHqlTestEntities.cs

+11-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,17 @@
22

33
namespace NHibernate.Test.Hql.EntityJoinHqlTestEntities
44
{
5-
public class EntityComplex
5+
public interface IEntityComplex
6+
{
7+
Guid Id { get; set; }
8+
int Version { get; set; }
9+
string Name { get; set; }
10+
string LazyProp { get; set; }
11+
EntityComplex SameTypeChild { get; set; }
12+
EntityComplex SameTypeChild2 { get; set; }
13+
}
14+
15+
public class EntityComplex : IEntityComplex
616
{
717
public virtual Guid Id { get; set; }
818
public virtual int Version { get; set; }

src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs

+20-24
Original file line numberDiff line numberDiff line change
@@ -702,12 +702,10 @@ void CreateFromJoinElement(
702702
// 2) an entity-join (join com.acme.User)
703703
//
704704
// so make the proper interpretation here...
705-
var entityJoinReferencedPersister = ResolveEntityJoinReferencedPersister(path);
706-
if (entityJoinReferencedPersister != null)
705+
// DOT node processing was moved to prefer implicit join path before probing for entity join
706+
if (path.Type == IDENT)
707707
{
708-
var entityJoin = CreateEntityJoin(entityJoinReferencedPersister, alias, joinType, with);
709-
((FromReferenceNode) path).FromElement = entityJoin;
710-
SetPropertyFetch(entityJoin, propertyFetch, alias);
708+
ProcessAsEntityJoin();
711709
return;
712710
}
713711
// The path AST should be a DotNode, and it should have been evaluated already.
@@ -725,6 +723,7 @@ void CreateFromJoinElement(
725723

726724
// Generate an explicit join for the root dot node. The implied joins will be collected and passed up
727725
// to the root dot node.
726+
dot.SkipSemiResolve = true;
728727
dot.Resolve( true, false, alias == null ? null : alias.Text );
729728

730729
FromElement fromElement;
@@ -738,7 +737,8 @@ void CreateFromJoinElement(
738737
fromElement = dot.GetImpliedJoin();
739738
if (fromElement == null)
740739
{
741-
throw new InvalidPathException("Invalid join: " + dot.Path);
740+
ProcessAsEntityJoin();
741+
return;
742742
}
743743
SetPropertyFetch(fromElement, propertyFetch, alias);
744744

@@ -762,6 +762,15 @@ void CreateFromJoinElement(
762762
{
763763
log.Debug("createFromJoinElement() : {0}", _printer.ShowAsString( fromElement, "-- join tree --" ));
764764
}
765+
766+
void ProcessAsEntityJoin()
767+
{
768+
var node = (FromReferenceNode) path;
769+
var entityJoinReferencedPersister = ResolveEntityJoinReferencedPersister(node);
770+
var entityJoin = CreateEntityJoin(entityJoinReferencedPersister, alias, joinType, with);
771+
node.FromElement = entityJoin;
772+
SetPropertyFetch(entityJoin, propertyFetch, alias);
773+
}
765774
}
766775

767776
private EntityJoinFromElement CreateEntityJoin(
@@ -790,42 +799,29 @@ private EntityJoinFromElement CreateEntityJoin(
790799
return join;
791800
}
792801

793-
private IQueryable ResolveEntityJoinReferencedPersister(IASTNode path)
802+
private IQueryable ResolveEntityJoinReferencedPersister(FromReferenceNode path)
794803
{
795-
string entityName = GetEntityJoinCandidateEntityName(path);
804+
string entityName = path.Path;
796805

797806
var persister = SessionFactoryHelper.FindQueryableUsingImports(entityName);
798807
if (persister == null && entityName != null)
799808
{
800809
var implementors = SessionFactoryHelper.Factory.GetImplementors(entityName);
801810
//Possible case - join on interface
802811
if (implementors.Length == 1)
803-
persister = SessionFactoryHelper.FindQueryableUsingImports(implementors[0]);
812+
persister = (IQueryable) SessionFactoryHelper.Factory.TryGetEntityPersister(implementors[0]);
804813
}
805814

806815
if (persister != null)
807816
return persister;
808817

809818
if (path.Type == IDENT)
810819
{
811-
// Since IDENT node is not expected for implicit join path, we can throw on not found persister
812820
throw new QuerySyntaxException(entityName + " is not mapped");
813821
}
814822

815-
return null;
816-
}
817-
818-
private static string GetEntityJoinCandidateEntityName(IASTNode path)
819-
{
820-
switch (path.Type)
821-
{
822-
case IDENT:
823-
return ((IdentNode) path).Path;
824-
case DOT:
825-
return ASTUtil.GetPathText(path);
826-
}
827-
828-
return null;
823+
//Keep old exception for DOT node
824+
throw new InvalidPathException("Invalid join: " + entityName);
829825
}
830826

831827
private static string GetPropertyPath(DotNode dotNode, IASTNode alias)

src/NHibernate/Hql/Ast/ANTLR/Tree/DotNode.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ public string PropertyPath
124124
set { _propertyPath = value; }
125125
}
126126

127+
internal bool SkipSemiResolve { get; set; }
128+
127129
public override void SetScalarColumnText(int i)
128130
{
129131
string[] sqlColumns = GetColumns();
@@ -200,7 +202,7 @@ public override void Resolve(bool generateJoin, bool implicitJoin, string classA
200202
// this might be a Java constant.
201203
if ( propertyType == null )
202204
{
203-
if ( parent == null )
205+
if (parent == null && !SkipSemiResolve)
204206
{
205207
Walker.LiteralProcessor.LookupConstant( this );
206208
}

0 commit comments

Comments
 (0)