Skip to content

Commit c6efeb8

Browse files
Add a test case for #3306
1 parent c49a2a9 commit c6efeb8

File tree

2 files changed

+271
-0
lines changed

2 files changed

+271
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System.Collections.Generic;
2+
3+
namespace NHibernate.Test.NHSpecificTest.GH3306
4+
{
5+
// Entities copied from https://github.com/craigfowler/NHibernateInvalidSql then adjusted.
6+
7+
public class UEntity
8+
{
9+
public virtual UKey Id { get; set; }
10+
11+
public virtual ISet<LEntity> LEntities { get; protected set; } = new HashSet<LEntity>();
12+
13+
public override string ToString() => $"[{nameof(UEntity)}#{Id}]";
14+
}
15+
16+
/// <summary>
17+
/// Represents the ID of a <see cref="UEntity"/>. This is a reference to a <see cref="PEntity"/> and an ID which
18+
/// is unique only within a <see cref="PEntity"/>.
19+
/// </summary>
20+
public class UKey
21+
{
22+
public virtual PEntity PEntity { get; set; }
23+
24+
public virtual long Id { get; set; }
25+
26+
public override string ToString() => $"[{nameof(UKey)}#{nameof(PEntity)}={PEntity}, {nameof(Id)}={Id}]";
27+
28+
public override bool Equals(object obj)
29+
{
30+
if (obj is null) return false;
31+
if (PEntity is null) return false;
32+
if (!(obj is UKey uKey)) return false;
33+
if (uKey.PEntity is null) return false;
34+
35+
return uKey.Id == Id && uKey.PEntity.Id == PEntity.Id;
36+
}
37+
38+
public override int GetHashCode() => Id.GetHashCode() ^ (PEntity?.Id).GetValueOrDefault().GetHashCode();
39+
}
40+
41+
public class PEntity
42+
{
43+
public virtual long Id { get; set; }
44+
45+
public virtual string PStatus { get; set; }
46+
47+
public override string ToString() => $"[{nameof(PEntity)}#{Id}]";
48+
}
49+
50+
public class DEntity
51+
{
52+
public virtual long Id { get; set; }
53+
54+
public virtual long LinkedEntityId { get; set; }
55+
56+
public virtual string LinkedEntityType { get; set; }
57+
58+
public virtual TEntity LinkedTEntity { get; protected set; }
59+
}
60+
61+
public class LEntity
62+
{
63+
public virtual long Id { get; set; }
64+
65+
public virtual string TStatus { get; set; }
66+
67+
public virtual TEntity TEntity { get; set; }
68+
69+
public virtual UEntity UEntity { get; set; }
70+
}
71+
72+
public class TEntity
73+
{
74+
public virtual long Id { get; set; }
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using NHibernate.Cfg.MappingSchema;
5+
using NHibernate.Mapping.ByCode;
6+
using NUnit.Framework;
7+
8+
namespace NHibernate.Test.NHSpecificTest.GH3306
9+
{
10+
/// <summary>
11+
/// Fixture using 'by code' mappings
12+
/// </summary>
13+
/// <remarks>
14+
/// This fixture is identical to <see cref="Fixture" /> except the <see cref="Entity" /> mapping is performed
15+
/// by code in the GetMappings method, and does not require the <c>Mappings.hbm.xml</c> file. Use this approach
16+
/// if you prefer.
17+
/// </remarks>
18+
[TestFixture]
19+
public class ByCodeFixture : TestCaseMappingByCode
20+
{
21+
protected override HbmMapping GetMappings()
22+
{
23+
// Mapping copied from https://github.com/craigfowler/NHibernateInvalidSql then adjusted.
24+
var mapper = new ModelMapper();
25+
mapper.Class<UEntity>(rc =>
26+
{
27+
rc.ComponentAsId(x => x.Id, m =>
28+
{
29+
m.ManyToOne(x => x.PEntity, p => p.Column("p_id"));
30+
m.Property(x => x.Id, p => p.Column("id"));
31+
});
32+
33+
rc.Set(x => x.LEntities,
34+
m =>
35+
{
36+
m.Inverse(true);
37+
m.Key(k =>
38+
{
39+
k.Columns(c => c.Name("p_id"), c => c.Name("u_id"));
40+
k.NotNullable(true);
41+
});
42+
},
43+
a => a.OneToMany());
44+
});
45+
46+
mapper.Class<PEntity>(rc =>
47+
{
48+
rc.Id(x => x.Id, p => p.Column("id"));
49+
rc.Property(x => x.PStatus, p => p.Column("p_status"));
50+
});
51+
52+
mapper.Class<DEntity>(rc =>
53+
{
54+
rc.Id(x => x.Id, p => p.Column("id"));
55+
rc.Property(x => x.LinkedEntityId, p => p.Column("linked_entity_id"));
56+
rc.Property(x => x.LinkedEntityType, p => p.Column("linked_entity_type"));
57+
58+
rc.ManyToOne(x => x.LinkedTEntity,
59+
m =>
60+
{
61+
m.Cascade(Mapping.ByCode.Cascade.None);
62+
m.Update(false);
63+
m.Insert(false);
64+
m.NotFound(NotFoundMode.Ignore);
65+
m.NotNullable(false);
66+
m.Fetch(FetchKind.Join);
67+
m.Formula("CASE WHEN linked_entity_type = 'T' THEN linked_entity_id ELSE NULL END");
68+
});
69+
});
70+
71+
mapper.Class<LEntity>(rc =>
72+
{
73+
rc.Id(x => x.Id, p => p.Column("id"));
74+
rc.Property(x => x.TStatus, p => p.Column("t_status"));
75+
rc.ManyToOne(x => x.TEntity, m => m.Column("t_id"));
76+
rc.ManyToOne(x => x.UEntity, m => m.Columns(c => c.Name("p_id"), c => c.Name("u_id")));
77+
});
78+
79+
mapper.Class<TEntity>(rc =>
80+
{
81+
rc.Id(x => x.Id, p => p.Column("id"));
82+
});
83+
84+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
85+
}
86+
87+
protected override void OnSetUp()
88+
{
89+
using (var session = OpenSession())
90+
using (var transaction = session.BeginTransaction())
91+
{
92+
/*INSERT INTO t_entity(id) VALUES(1), (2), (3);
93+
INSERT INTO p_entity(id, p_status) VALUES(1, 'Good PStatus'), (2, 'Bad PStatus');
94+
INSERT INTO u_entity(p_id, id) VALUES(1, 1), (1, 2), (1, 3), (2, 1);
95+
INSERT INTO l_entity(id, t_status, t_id, p_id, u_id) VALUES
96+
(1, 'Good TStatus', 1, 1, 1),
97+
(2, 'Bad TStatus', 1, 3, 2),
98+
(3, 'Good TStatus', 1, 2, 2),
99+
(4, 'Good TStatus', 2, 2, 3);
100+
INSERT INTO d_entity(id, linked_entity_id, linked_entity_type) VALUES
101+
(1, 1, 'T'),
102+
(2, 2, 'T'),
103+
(3, 3, 'T'),
104+
(4, 0, 'Z');*/
105+
// Adapted from the SQL provided by https://github.com/craigfowler/NHibernateInvalidSql.
106+
var t1 = new TEntity { Id = 1 };
107+
var t2 = new TEntity { Id = 2 };
108+
var t3 = new TEntity { Id = 3 };
109+
session.Save(t1);
110+
session.Save(t2);
111+
session.Save(t3);
112+
113+
var goodP = new PEntity { Id = 1, PStatus = "Good PStatus" };
114+
var badP = new PEntity { Id = 2, PStatus = "Bad PStatus" };
115+
session.Save(goodP);
116+
session.Save(badP);
117+
118+
var u1 = new UEntity { Id = new UKey { PEntity = goodP, Id = 1 } };
119+
var u2 = new UEntity { Id = new UKey { PEntity = goodP, Id = 2 } };
120+
var u3 = new UEntity { Id = new UKey { PEntity = goodP, Id = 3 } };
121+
var u4 = new UEntity { Id = new UKey { PEntity = badP, Id = 1 } };
122+
session.Save(u1);
123+
session.Save(u2);
124+
session.Save(u3);
125+
session.Save(u4);
126+
127+
session.Save(new LEntity { Id = 1, TStatus = "Good TStatus", TEntity = t1, UEntity = u1 });
128+
session.Flush();
129+
130+
session.CreateSQLQuery("insert LEntity (id, t_status, t_id, p_id, u_id) values (2, 'Bad TStatus', 1, 3, 2)").ExecuteUpdate();
131+
session.CreateSQLQuery("insert LEntity (id, t_status, t_id, p_id, u_id) values (3, 'Good TStatus', 1, 2, 2)").ExecuteUpdate();
132+
session.CreateSQLQuery("insert LEntity (id, t_status, t_id, p_id, u_id) values (4, 'Good TStatus', 2, 2, 3)").ExecuteUpdate();
133+
134+
session.CreateSQLQuery("insert DEntity (id, linked_entity_id, linked_entity_type) values (1, 1, 'T')").ExecuteUpdate();
135+
session.CreateSQLQuery("insert DEntity (id, linked_entity_id, linked_entity_type) values (2, 2, 'T')").ExecuteUpdate();
136+
session.CreateSQLQuery("insert DEntity (id, linked_entity_id, linked_entity_type) values (3, 3, 'T')").ExecuteUpdate();
137+
session.CreateSQLQuery("insert DEntity (id, linked_entity_id, linked_entity_type) values (4, 0, 'Z')").ExecuteUpdate();
138+
139+
transaction.Commit();
140+
}
141+
}
142+
143+
protected override void OnTearDown()
144+
{
145+
using (var session = OpenSession())
146+
using (var transaction = session.BeginTransaction())
147+
{
148+
// The HQL delete does all the job inside the database without loading the entities, but it does
149+
// not handle delete order for avoiding violating constraints if any. Use
150+
// session.Delete("from System.Object");
151+
// instead if in need of having NHbernate ordering the deletes, but this will cause
152+
// loading the entities in the session.
153+
session.CreateQuery("delete from DEntity").ExecuteUpdate();
154+
session.CreateQuery("delete from LEntity").ExecuteUpdate();
155+
session.CreateQuery("delete from UEntity").ExecuteUpdate();
156+
session.CreateQuery("delete from System.Object").ExecuteUpdate();
157+
158+
transaction.Commit();
159+
}
160+
}
161+
162+
[Test]
163+
public void InvalidSql()
164+
{
165+
using (var session = OpenSession())
166+
using (var transaction = session.BeginTransaction())
167+
{
168+
// Test copied from https://github.com/craigfowler/NHibernateInvalidSql then adjusted.
169+
var pEntities = session.Query<PEntity>().Where(p => p.PStatus == "Good PStatus");
170+
var dEntities = session.Query<DEntity>().Where(d => d.Id == 1);
171+
172+
// The reason for the three separate .Where clauses is that in our own impl, these three expressions come from three
173+
// separate & independent specification classes. Each represents an independent business rule.
174+
var query = session
175+
.Query<UEntity>()
176+
.Where(u => pEntities.Any(p => u.Id.PEntity == p))
177+
.Where(u => dEntities.Any(d => u.LEntities.Any(l => l.TEntity == d.LinkedTEntity)))
178+
// The part of this query which states !u.LEntities.Any() is not strictly necessary, but works
179+
// around some other suboptimal SQL in other scenarios.
180+
.Where(u => !u.LEntities.Any() || u.LEntities.All(l => l.TStatus != "Bad TStatus"));
181+
182+
List<UEntity> uEntities = null;
183+
Assert.That(
184+
() =>
185+
{
186+
uEntities = query.ToList();
187+
}, Throws.Nothing);
188+
Assert.That(uEntities, Has.Count.EqualTo(1));
189+
Assert.That(uEntities[0].Id, Has.Property(nameof(UKey.Id)).EqualTo(1).And.Property(nameof(UKey.PEntity)).Property(nameof(PEntity.Id)).EqualTo(1));
190+
191+
transaction.Commit();
192+
}
193+
}
194+
}
195+
}

0 commit comments

Comments
 (0)