Skip to content

Commit 4533ef2

Browse files
committed
Fix NH-2705 and NH-2615
SVN: trunk@5830
1 parent c482001 commit 4533ef2

13 files changed

+273
-45
lines changed

src/NHibernate.Test/NHSpecificTest/NH2705/ItemBase.cs

-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,4 @@ public class ItemBase
77
}
88

99
public class ItemWithComponentSubItem : ItemBase {}
10-
11-
public class ItemWithManyToOneSubItem : ItemBase {}
1210
}

src/NHibernate.Test/NHSpecificTest/NH2705/Mappings.hbm.xml

-12
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,8 @@
1414
<many-to-one class="SubItemDetails" name="Details" column="SubItemDetails_id"/>
1515
</component>
1616
</joined-subclass>
17-
<joined-subclass name="ItemWithManyToOneSubItem">
18-
<key column="ItemBase_id"/>
19-
<many-to-one class="SubItemEntity" name="SubItem" column="SubItem_id"/>
20-
</joined-subclass>
2117
</class>
2218

23-
<class name="SubItemEntity">
24-
<id name="Id" type="int">
25-
<generator class="native" />
26-
</id>
27-
<property name="Name" />
28-
<many-to-one class="SubItemDetails" name="Details" column="SubItemDetails_id" />
29-
</class>
30-
3119
<class name="SubItemDetails">
3220
<id name="Id" type="int">
3321
<generator class="native" />
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
namespace NHibernate.Test.NHSpecificTest.NH2705
22
{
3+
// NOTE: an Entity and a Component in the same hierarchy is not supported
4+
// we are using this trick just to ""simplify"" the test.
35
public class SubItemBase
46
{
57
public virtual string Name { get; set; }
68
public virtual SubItemDetails Details { get; set; }
79
}
810

911
public class SubItemComponent : SubItemBase {}
10-
11-
public class SubItemEntity : SubItemBase
12-
{
13-
public virtual int Id { get; set; }
14-
}
1512
}

src/NHibernate.Test/NHSpecificTest/NH2705/Test.cs

+22-6
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
namespace NHibernate.Test.NHSpecificTest.NH2705
1010
{
11-
[TestFixture, Ignore("Not fixed yet")]
11+
[TestFixture]
1212
public class Test : BugTestCase
1313
{
1414
private static IEnumerable<T> GetAndFetch<T>(string name, ISession session) where T : ItemBase
@@ -30,22 +30,26 @@ public void Fetch_OnComponent_ShouldNotThrow()
3030
}
3131

3232
[Test]
33-
public void Fetch_OnManyToOne_ShouldNotThrow()
33+
public void HqlQueryWithFetch_WhenDerivedClassesUseComponentAndManyToOne_DoesNotGenerateInvalidSql()
3434
{
3535
using (ISession s = OpenSession())
3636
{
37-
Executing.This(() => GetAndFetch<ItemWithManyToOneSubItem>("hello", s)).Should().NotThrow();
37+
using (var log = new SqlLogSpy())
38+
{
39+
Executing.This(() => s.CreateQuery("from ItemWithComponentSubItem i left join fetch i.SubItem").List()
40+
).Should().NotThrow();
41+
}
3842
}
3943
}
4044

4145
[Test]
42-
public void HqlQueryWithFetch_WhenDerivedClassesUseComponentAndManyToOne_DoesNotGenerateInvalidSql()
46+
public void HqlQueryWithFetch_WhenDerivedClassesUseComponentAndEagerFetchManyToOne_DoesNotGenerateInvalidSql()
4347
{
4448
using (ISession s = OpenSession())
4549
{
4650
using (var log = new SqlLogSpy())
4751
{
48-
Executing.This(() => s.CreateQuery("from ItemBase i left join fetch i.SubItem").List()
52+
Executing.This(() => s.CreateQuery("from ItemWithComponentSubItem i left join fetch i.SubItem.Details").List()
4953
).Should().NotThrow();
5054
}
5155
}
@@ -64,11 +68,23 @@ public void LinqQueryWithFetch_WhenDerivedClassesUseComponentAndManyToOne_DoesNo
6468

6569

6670
// fetching second level properties should work too
67-
Executing.This(() => s.Query<ItemBase>()
71+
Executing.This(() => s.Query<ItemWithComponentSubItem>()
6872
.Fetch(p => p.SubItem).ThenFetch(p => p.Details).ToList()
6973
).Should().NotThrow();
7074
}
7175
}
7276
}
77+
78+
[Test, Ignore("Locked by re-linq")]
79+
public void LinqQueryWithFetch_WhenDerivedClassesUseComponentAndEagerFetchManyToOne_DoesNotGenerateInvalidSql()
80+
{
81+
using (ISession s = OpenSession())
82+
{
83+
using (var log = new SqlLogSpy())
84+
{
85+
Executing.This(() => s.Query<ItemWithComponentSubItem>().Fetch(p => p.SubItem.Details).ToList()).Should().NotThrow();
86+
}
87+
}
88+
}
7389
}
7490
}

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

+18-11
Original file line numberDiff line numberDiff line change
@@ -678,23 +678,30 @@ void CreateFromJoinElement(
678678
// to the root dot node.
679679
dot.Resolve( true, false, alias == null ? null : alias.Text );
680680

681-
FromElement fromElement = dot.GetImpliedJoin();
682-
683-
if (fromElement == null)
681+
FromElement fromElement;
682+
if (dot.DataType != null && dot.DataType.IsComponentType)
684683
{
685-
throw new InvalidPathException("Invalid join: " + dot.Path);
684+
var factory = new FromElementFactory(CurrentFromClause, dot.GetLhs().FromElement, dot.PropertyPath, alias == null ? null : alias.Text, null, false);
685+
fromElement = factory.CreateComponentJoin((ComponentType) dot.DataType);
686686
}
687-
688-
fromElement.SetAllPropertyFetch(propertyFetch!=null);
689-
690-
if ( with != null )
687+
else
691688
{
692-
if ( fetch )
689+
fromElement = dot.GetImpliedJoin();
690+
if (fromElement == null)
693691
{
694-
throw new SemanticException( "with-clause not allowed on fetched associations; use filters" );
692+
throw new InvalidPathException("Invalid join: " + dot.Path);
695693
}
694+
fromElement.SetAllPropertyFetch(propertyFetch != null);
696695

697-
HandleWithFragment( fromElement, with );
696+
if (with != null)
697+
{
698+
if (fetch)
699+
{
700+
throw new SemanticException("with-clause not allowed on fetched associations; use filters");
701+
}
702+
703+
HandleWithFragment(fromElement, with);
704+
}
698705
}
699706

700707
if ( log.IsDebugEnabled )
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
using System;
2+
using System.Text;
3+
using NHibernate.Persister.Collection;
4+
using NHibernate.Persister.Entity;
5+
using NHibernate.Type;
6+
using NHibernate.Util;
7+
8+
namespace NHibernate.Hql.Ast.ANTLR.Tree
9+
{
10+
public class ComponentJoin : FromElement
11+
{
12+
private readonly string columns;
13+
private readonly string componentPath;
14+
private readonly string componentProperty;
15+
private readonly ComponentType componentType;
16+
17+
public ComponentJoin(FromClause fromClause, FromElement origin, string alias, string componentPath, ComponentType componentType)
18+
: base(fromClause, origin, alias)
19+
{
20+
this.componentPath = componentPath;
21+
this.componentType = componentType;
22+
componentProperty = StringHelper.Unqualify(componentPath);
23+
fromClause.AddJoinByPathMap(componentPath, this);
24+
InitializeComponentJoin(new ComponentFromElementType(this));
25+
26+
string[] cols = origin.GetPropertyMapping("").ToColumns(TableAlias, componentProperty);
27+
columns = string.Join(", ", cols);
28+
}
29+
30+
public string ComponentPath
31+
{
32+
get { return componentPath; }
33+
}
34+
35+
public ComponentType ComponentType
36+
{
37+
get { return componentType; }
38+
}
39+
40+
public string ComponentProperty
41+
{
42+
get { return componentProperty; }
43+
}
44+
45+
public override IType DataType
46+
{
47+
get { return ComponentType; }
48+
set { base.DataType = value; }
49+
}
50+
51+
public override string GetIdentityColumn()
52+
{
53+
return columns;
54+
}
55+
56+
public override string GetDisplayText()
57+
{
58+
return "ComponentJoin{path=" + ComponentPath + ", type=" + componentType.ReturnedClass + "}";
59+
}
60+
61+
#region Nested type: ComponentFromElementType
62+
63+
public class ComponentFromElementType : FromElementType
64+
{
65+
private readonly ComponentJoin fromElement;
66+
private readonly IPropertyMapping propertyMapping;
67+
68+
public ComponentFromElementType(ComponentJoin fromElement)
69+
: base(fromElement)
70+
{
71+
this.fromElement = fromElement;
72+
propertyMapping = new ComponentPropertyMapping(this);
73+
}
74+
75+
public ComponentJoin FromElement
76+
{
77+
get { return fromElement; }
78+
}
79+
80+
public override IType DataType
81+
{
82+
get { return fromElement.ComponentType; }
83+
}
84+
85+
public override IQueryableCollection QueryableCollection
86+
{
87+
get { return null; }
88+
set { base.QueryableCollection = value; }
89+
}
90+
91+
public override IPropertyMapping GetPropertyMapping(string propertyName)
92+
{
93+
return propertyMapping;
94+
}
95+
96+
public override IType GetPropertyType(string propertyName, string propertyPath)
97+
{
98+
int index = fromElement.ComponentType.GetPropertyIndex(propertyName);
99+
return fromElement.ComponentType.Subtypes[index];
100+
}
101+
102+
public override string RenderScalarIdentifierSelect(int i)
103+
{
104+
String[] cols = GetBasePropertyMapping().ToColumns(fromElement.TableAlias, fromElement.ComponentProperty);
105+
var buf = new StringBuilder();
106+
// For property references generate <tablealias>.<columnname> as <projectionalias>
107+
for (int j = 0; j < cols.Length; j++)
108+
{
109+
string column = cols[j];
110+
if (j > 0)
111+
{
112+
buf.Append(", ");
113+
}
114+
buf.Append(column).Append(" as ").Append(NameGenerator.ScalarName(i, j));
115+
}
116+
return buf.ToString();
117+
}
118+
119+
protected IPropertyMapping GetBasePropertyMapping()
120+
{
121+
return fromElement.Origin.GetPropertyMapping("");
122+
}
123+
124+
#region Nested type: ComponentPropertyMapping
125+
126+
private class ComponentPropertyMapping : IPropertyMapping
127+
{
128+
private readonly ComponentFromElementType fromElementType;
129+
130+
public ComponentPropertyMapping(ComponentFromElementType fromElementType)
131+
{
132+
this.fromElementType = fromElementType;
133+
}
134+
135+
#region IPropertyMapping Members
136+
137+
public IType Type
138+
{
139+
get { return fromElementType.FromElement.ComponentType; }
140+
}
141+
142+
public IType ToType(string propertyName)
143+
{
144+
return fromElementType.GetBasePropertyMapping().ToType(GetPropertyPath(propertyName));
145+
}
146+
147+
public bool TryToType(string propertyName, out IType type)
148+
{
149+
return fromElementType.GetBasePropertyMapping().TryToType(GetPropertyPath(propertyName), out type);
150+
}
151+
152+
public string[] ToColumns(string alias, string propertyName)
153+
{
154+
return fromElementType.GetBasePropertyMapping().ToColumns(alias, GetPropertyPath(propertyName));
155+
}
156+
157+
public string[] ToColumns(string propertyName)
158+
{
159+
return fromElementType.GetBasePropertyMapping().ToColumns(GetPropertyPath(propertyName));
160+
}
161+
162+
#endregion
163+
164+
private string GetPropertyPath(string propertyName)
165+
{
166+
return fromElementType.FromElement.ComponentPath + '.' + propertyName;
167+
}
168+
}
169+
170+
#endregion
171+
}
172+
173+
#endregion
174+
}
175+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ public void RegisterFromElement(FromElement element)
342342
string tableAlias = element.TableAlias;
343343
if (tableAlias != null)
344344
{
345-
_fromElementByTableAlias.Add(tableAlias, element);
345+
_fromElementByTableAlias[tableAlias] = element;
346346
}
347347
}
348348

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

+25-2
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,33 @@ public class FromElement : HqlSqlWalkerNode, IDisplayableNode, IParameterContain
3939
private string _withClauseFragment;
4040
private string _withClauseJoinAlias;
4141
private bool _filter;
42-
42+
private IToken _token;
4343

4444
public FromElement(IToken token) : base(token)
4545
{
46+
_token= token;
47+
}
48+
49+
/// <summary>
50+
/// Constructor form used to initialize <see cref="ComponentJoin"/>.
51+
/// </summary>
52+
/// <param name="fromClause">The FROM clause to which this element belongs.</param>
53+
/// <param name="origin">The origin (LHS) of this element.</param>
54+
/// <param name="alias">The alias applied to this element.</param>
55+
protected FromElement(FromClause fromClause,FromElement origin,string alias):this(origin._token)
56+
{
57+
_fromClause = fromClause;
58+
_origin = origin;
59+
_classAlias = alias;
60+
_tableAlias = origin.TableAlias;
61+
base.Initialize(fromClause.Walker);
62+
}
63+
64+
protected void InitializeComponentJoin(FromElementType elementType)
65+
{
66+
_elementType = elementType;
67+
_fromClause.RegisterFromElement(this);
68+
_initialized = true;
4669
}
4770

4871
public void SetAllPropertyFetch(bool fetch)
@@ -429,7 +452,7 @@ public IType GetPropertyType(string propertyName, string propertyPath)
429452
return _elementType.GetPropertyType(propertyName, propertyPath);
430453
}
431454

432-
public string GetIdentityColumn()
455+
public virtual string GetIdentityColumn()
433456
{
434457
CheckInitialized();
435458
string table = TableAlias;

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

+5
Original file line numberDiff line numberDiff line change
@@ -562,5 +562,10 @@ private string[] Columns
562562
}
563563
}
564564

565+
public FromElement CreateComponentJoin(ComponentType type)
566+
{
567+
// need to create a "place holder" from-element that can store the component/alias for this component join
568+
return new ComponentJoin(_fromClause, _origin, _classAlias, _path, type);
569+
}
565570
}
566571
}

0 commit comments

Comments
 (0)