Index: src/NHibernate.Test/Join/User.cs =================================================================== --- src/NHibernate.Test/Join/User.cs (revision 0) +++ src/NHibernate.Test/Join/User.cs (revision 0) @@ -0,0 +1,17 @@ +using System; + +namespace NHibernate.Test.Join +{ + public class User : Person + { + private string _Silly; + + private string _Login; + public virtual string Login + { + get { return _Login; } + set { _Login = value; } + } + + } +} Index: src/NHibernate.Test/Join/Employee.cs =================================================================== --- src/NHibernate.Test/Join/Employee.cs (revision 0) +++ src/NHibernate.Test/Join/Employee.cs (revision 0) @@ -0,0 +1,29 @@ +using System; + +namespace NHibernate.Test.Join +{ + public class Employee : Person + { + private string _Title; + public virtual string Title + { + get { return _Title; } + set { _Title = value; } + } + + private Employee _Manager; + public virtual Employee Manager + { + get { return _Manager; } + set { _Manager = value; } + } + + private decimal _Salary; + public virtual decimal Salary + { + get { return _Salary; } + set { _Salary = value; } + } + + } +} Index: src/NHibernate.Test/Join/JoinTest.cs =================================================================== --- src/NHibernate.Test/Join/JoinTest.cs (revision 0) +++ src/NHibernate.Test/Join/JoinTest.cs (revision 0) @@ -0,0 +1,398 @@ +using log4net; +using NUnit.Framework; +using System; +using System.Collections; +using System.Data; + +namespace NHibernate.Test.Join +{ + using NHibernate.Test.Subclass; + + [TestFixture] + public class JoinTest : TestCase + { + private static ILog log = LogManager.GetLogger(typeof(JoinTest)); + + protected override string MappingsAssembly + { + get { return "NHibernate.Test"; } + } + + protected override IList Mappings + { + get + { + return new string[] { + "Join.Person.hbm.xml", + "Subclass.Subclass.hbm.xml" + }; + } + } + + ISession s; + ITransaction t; + + protected override void OnSetUp() + { + s = OpenSession(); + //t = s.BeginTransaction(); + + objectsNeedDeleting.Clear(); + } + + protected override void OnTearDown() + { + s.Flush(); + s.Clear(); + try + { + foreach (object obj in objectsNeedDeleting) + { + s.Delete(obj); + } + s.Flush(); + } + finally + { + //t.Commit(); + s.Close(); + } + + t = null; + s = null; + } + + private IList objectsNeedDeleting = new ArrayList(); + + [Test] + public void TestSequentialSelects() + { + Employee mark = new Employee(); + mark.Name = "Mark"; + mark.Title = "internal sales"; + mark.Sex = 'M'; + mark.Address = "buckhead"; + mark.Zip = "30305"; + mark.Country = "USA"; + + Customer joe = new Customer(); + joe.Name = "Joe"; + joe.Address = "San Francisco"; + joe.Zip = "54353"; + joe.Country = "USA"; + joe.Comments = "very demanding"; + joe.Sex = 'M'; + joe.Salesperson = mark; + + Person yomomma = new Person(); + yomomma.Name = "mom"; + yomomma.Sex = 'F'; + + s.Save(yomomma); + s.Save(mark); + s.Save(joe); + + objectsNeedDeleting.Add(yomomma); + objectsNeedDeleting.Add(mark); + objectsNeedDeleting.Add(joe); + + s.Flush(); + s.Clear(); + + Person p = s.Get(yomomma.Id); + Assert.AreEqual(yomomma.Name, p.Name); + Assert.AreEqual(yomomma.Sex, p.Sex); + s.Clear(); + + // Copied from H3. Don't really know what it is testing + //Assert.AreEqual(0, s.CreateQuery("from System.Serializable").List().Count); + + Assert.AreEqual(3, s.CreateQuery("from Person").List().Count); + Assert.AreEqual(1, s.CreateQuery("from Person p where p.class is null").List().Count); + Assert.AreEqual(1, s.CreateQuery("from Person p where p.class = Customer").List().Count); + Assert.AreEqual(1, s.CreateQuery("from Customer c").List().Count); + s.Clear(); + + IList customers = s.CreateQuery("from Customer c left join fetch c.Salesperson").List(); + foreach (Customer c in customers) + { + Assert.IsTrue(NHibernateUtil.IsInitialized(c.Salesperson)); + Assert.AreEqual("Mark", c.Salesperson.Name); + } + Assert.AreEqual(1, customers.Count); + s.Clear(); + + mark = (Employee)s.Get(typeof(Employee), mark.Id); + joe = (Customer)s.Get(typeof(Customer), joe.Id); + + mark.Zip = "30306"; + s.Flush(); + s.Clear(); + Assert.AreEqual(1, s.CreateQuery("from Person p where p.Zip = '30306'").List().Count); + + // Clean up done in TearDown + } + + [Test] + [Ignore] + public void TestSequentialSelectsOptionalData() + { + // The "optional" attribute on does not yet work + + User jesus = new User(); + jesus.Name = "Jesus Olvera y Martinez"; + jesus.Sex = 'M'; + + s.Save(jesus); + //objectsNeedDeleting.Add(jesus); + + Assert.AreEqual(1, s.CreateQuery("from Person").List().Count); + Assert.AreEqual(0, s.CreateQuery("from Person p where p.class is null").List().Count); + Assert.AreEqual(1, s.CreateQuery("from Person p where p.class = User").List().Count); + Assert.AreEqual(1, s.CreateQuery("from User u").List().Count); + s.Clear(); + + // Remove the optional row from the join table and requery the User obj + IDbCommand cmd = s.Connection.CreateCommand(); + cmd.CommandText = "delete from t_user"; + cmd.CommandType = CommandType.Text; + cmd.ExecuteNonQuery(); + s.Clear(); + + // Clean up the test data + s.Delete(jesus); + s.Flush(); + + Assert.AreEqual(0, s.CreateQuery("from Person").List().Count); + } + + private Person CreatePerson(string name) + { + Person p = new Person(); + p.Name = name; + p.Sex = 'M'; + p.Address = "123 Some Street"; + p.Zip = "12345"; + p.Country = "Canada"; + p.HomePhone = "555-1234"; + p.BusinessPhone = "555-4321"; + + return p; + } + + protected bool PersonsAreEqual(Person x, Person y) + { + if (!string.Equals(x.Name, y.Name)) return false; + if (x.Sex != y.Sex) return false; + if (!string.Equals(x.Address, y.Address)) return false; + if (!string.Equals(x.Zip, y.Zip)) return false; + if (!string.Equals(x.Country, y.Country)) return false; + if (!string.Equals(x.HomePhone, y.HomePhone)) return false; + if (!string.Equals(x.BusinessPhone, y.BusinessPhone)) return false; + + return true; + } + + private Person[] CreateAndInsertPersons(int count) + { + Person[] result = new Person[count]; + + for (int i = 0; i < count; i++) + { + result[i] = CreatePerson("Person " + i.ToString()); + s.Save(result[i]); + objectsNeedDeleting.Add(result[i]); + } + + s.Flush(); + s.Clear(); + + return result; + } + + [Test] + public void TestRetrieveUsingGet() + { + // Create a new person John + Person john = CreatePerson("John"); + + s.Save(john); + objectsNeedDeleting.Add(john); + s.Flush(); + s.Clear(); + + Person p = (Person)s.Get(typeof(Person), john.Id); + Assert.IsTrue(PersonsAreEqual(john, p)); + } + + [Test] + public void TestRetrieveUsingCriteriaInterface() + { + Person[] people = CreateAndInsertPersons(3); + + ICriteria criteria = s.CreateCriteria(typeof(Person)) + .Add(Expression.Expression.Eq("Name", people[1].Name)); + IList list = criteria.List(); + + Assert.AreEqual(1, list.Count); + Assert.IsTrue(PersonsAreEqual(people[1], (Person)list[0])); + } + + [Test] + public void TestRetrieveUsingHql() + { + Person[] people = CreateAndInsertPersons(3); + + IQuery query = s.CreateQuery("from Person p where p.Name = :name") + .SetParameter("name", people[1].Name); + IList list = query.List(); + + Assert.AreEqual(1, list.Count); + Assert.IsTrue(PersonsAreEqual(people[1], (Person)list[0])); + } + + private Employee CreateEmployee(string name, string title) + { + Employee p = new Employee(); + p.Name = name; + p.Sex = 'M'; + p.Address = "123 Some Street"; + p.Zip = "12345"; + p.Country = "Canada"; + p.HomePhone = "555-1234"; + p.BusinessPhone = "555-4321"; + + p.Title = title; + p.Salary = 100; + + return p; + } + + private Employee[] CreateAndInsertEmployees(int count) + { + Employee[] result = new Employee[count]; + + for (int i = 0; i < count; i++) + { + result[i] = CreateEmployee("Employee " + i.ToString(), "Title " + i.ToString()); + s.Save(result[i]); + objectsNeedDeleting.Add(result[i]); + } + + s.Flush(); + s.Clear(); + + return result; + } + + + [Test] + public void TestSimpleInsertAndRetrieveEmployee() + { + // Create a new employee Jack + Employee jack = CreateEmployee("Jack", "Boss"); + + s.Save(jack); + objectsNeedDeleting.Add(jack); + s.Flush(); + s.Clear(); + + IList list = s.CreateQuery("from Person p where p.Id = :id") + .SetParameter("id", jack.Id) + .List(); + } + + [Test] + public void TestDeleteUsingHql() + { + Person[] people = new Person[3]; + for (int i = 0; i < people.Length; i++) + { + people[i] = CreatePerson(string.Format("Person {0}", i + 1)); + s.Save(people[i]); + objectsNeedDeleting.Add(people[i]); + } + + s.Flush(); + s.Clear(); + + s.Delete("from Person"); + s.Flush(); + + IList list = s.CreateQuery("from Person").List(); + objectsNeedDeleting = list; + Assert.AreEqual(0, list.Count); + } + + private bool EmployeesAreEqual(Employee x, Employee y) + { + if (!PersonsAreEqual(x, y)) return false; + if (!string.Equals(x.Title, y.Title)) return false; + if (x.Salary != y.Salary) return false; + + if (x.Manager != null && y.Manager != null) + { + return x.Manager.Id == y.Manager.Id; + } + else if (x.Manager != null || y.Manager != null) + { + return false; + } + else // x.Manager and y.Manager are both null + { + return true; + } + } + + [Test] + public void TestUpdateEmployee() + { + Employee[] employees = CreateAndInsertEmployees(3); + + Employee emp0 = (Employee)s.Get(typeof(Employee), employees[0].Id); + Assert.IsNotNull(emp0); + emp0.Address = "Address"; + emp0.BusinessPhone = "BusinessPhone"; + emp0.Country = "Country"; + emp0.HomePhone = "HomePhone"; + emp0.Manager = employees[2]; + emp0.Name = "Name"; + emp0.Salary = 20000; + emp0.Title = "Title"; + emp0.Zip = "Zip"; + // Not updating emp0.Sex because it is marked update=false in the mapping file. + + s.Flush(); + s.Clear(); + + Employee emp0updated = (Employee)s.Get(typeof(Employee), employees[0].Id); + Assert.IsTrue(EmployeesAreEqual(emp0, emp0updated)); + } + + [Test] + public void Learn_SubclassBehavior() + { + SubclassOne one = new SubclassOne(); + one.TestDateTime = DateTime.Now; + + s.Save(one); + s.Flush(); + s.Clear(); + + SubclassOne result = (SubclassOne)s.Get(typeof(SubclassBase), one.Id); + Assert.IsNotNull(result); + Assert.IsTrue(result is SubclassOne); + + s.Delete(result); + } + + [Test] + public void PolymorphicGetByTypeofSuperclass() + { + Employee[] employees = CreateAndInsertEmployees(1); + Employee emp0 = (Employee)s.Get(typeof(Person), employees[0].Id); + Assert.IsNotNull(emp0); + Assert.IsTrue(emp0 is Employee); + } + } +} Index: src/NHibernate.Test/Join/Customer.cs =================================================================== --- src/NHibernate.Test/Join/Customer.cs (revision 0) +++ src/NHibernate.Test/Join/Customer.cs (revision 0) @@ -0,0 +1,22 @@ +using System; + +namespace NHibernate.Test.Join +{ + public class Customer : Person + { + private Employee _Salesperson; + public virtual Employee Salesperson + { + get { return _Salesperson; } + set { _Salesperson = value; } + } + + private string _Comments; + public virtual string Comments + { + get { return _Comments; } + set { _Comments = value; } + } + + } +} Index: src/NHibernate.Test/Join/Person.cs =================================================================== --- src/NHibernate.Test/Join/Person.cs (revision 0) +++ src/NHibernate.Test/Join/Person.cs (revision 0) @@ -0,0 +1,63 @@ +using System; + +namespace NHibernate.Test.Join +{ + public class Person + { + private char _Sex; + public virtual char Sex + { + get { return _Sex; } + set { _Sex = value; } + } + + private long _Id; + public virtual long Id + { + get { return _Id; } + set { _Id = value; } + } + + private string _Name; + public virtual string Name + { + get { return _Name; } + set { _Name = value; } + } + + private string _Country; + public virtual string Country + { + get { return _Country; } + set { _Country = value; } + } + + private string _Zip; + public virtual string Zip + { + get { return _Zip; } + set { _Zip = value; } + } + + private string _Address; + public virtual string Address + { + get { return _Address; } + set { _Address = value; } + } + + private string _HomePhone; + public virtual string HomePhone + { + get { return _HomePhone; } + set { _HomePhone = value; } + } + + private string _BusinessPhone; + public virtual string BusinessPhone + { + get { return _BusinessPhone; } + set { _BusinessPhone = value; } + } + } +} Index: src/NHibernate.Test/Join/Person.hbm.xml =================================================================== --- src/NHibernate.Test/Join/Person.hbm.xml (revision 0) +++ src/NHibernate.Test/Join/Person.hbm.xml (revision 0) @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file Index: src/NHibernate.Test/Join/Simple.cs =================================================================== Index: src/NHibernate.Test/Join/Customer.cs =================================================================== --- src/NHibernate.Test/Join/Customer.cs (revision 0) +++ src/NHibernate.Test/Join/Customer.cs (revision 0) @@ -0,0 +1,22 @@ +using System; + +namespace NHibernate.Test.Join +{ + public class Customer : Person + { + private Employee _Salesperson; + public virtual Employee Salesperson + { + get { return _Salesperson; } + set { _Salesperson = value; } + } + + private string _Comments; + public virtual string Comments + { + get { return _Comments; } + set { _Comments = value; } + } + + } +} Index: src/NHibernate.Test/Join/Employee.cs =================================================================== --- src/NHibernate.Test/Join/Employee.cs (revision 0) +++ src/NHibernate.Test/Join/Employee.cs (revision 0) @@ -0,0 +1,29 @@ +using System; + +namespace NHibernate.Test.Join +{ + public class Employee : Person + { + private string _Title; + public virtual string Title + { + get { return _Title; } + set { _Title = value; } + } + + private Employee _Manager; + public virtual Employee Manager + { + get { return _Manager; } + set { _Manager = value; } + } + + private decimal _Salary; + public virtual decimal Salary + { + get { return _Salary; } + set { _Salary = value; } + } + + } +} Index: src/NHibernate.Test/Join/JoinTest.cs =================================================================== --- src/NHibernate.Test/Join/JoinTest.cs (revision 0) +++ src/NHibernate.Test/Join/JoinTest.cs (revision 0) @@ -0,0 +1,398 @@ +using log4net; +using NUnit.Framework; +using System; +using System.Collections; +using System.Data; + +namespace NHibernate.Test.Join +{ + using NHibernate.Test.Subclass; + + [TestFixture] + public class JoinTest : TestCase + { + private static ILog log = LogManager.GetLogger(typeof(JoinTest)); + + protected override string MappingsAssembly + { + get { return "NHibernate.Test"; } + } + + protected override IList Mappings + { + get + { + return new string[] { + "Join.Person.hbm.xml", + "Subclass.Subclass.hbm.xml" + }; + } + } + + ISession s; + ITransaction t; + + protected override void OnSetUp() + { + s = OpenSession(); + //t = s.BeginTransaction(); + + objectsNeedDeleting.Clear(); + } + + protected override void OnTearDown() + { + s.Flush(); + s.Clear(); + try + { + foreach (object obj in objectsNeedDeleting) + { + s.Delete(obj); + } + s.Flush(); + } + finally + { + //t.Commit(); + s.Close(); + } + + t = null; + s = null; + } + + private IList objectsNeedDeleting = new ArrayList(); + + [Test] + public void TestSequentialSelects() + { + Employee mark = new Employee(); + mark.Name = "Mark"; + mark.Title = "internal sales"; + mark.Sex = 'M'; + mark.Address = "buckhead"; + mark.Zip = "30305"; + mark.Country = "USA"; + + Customer joe = new Customer(); + joe.Name = "Joe"; + joe.Address = "San Francisco"; + joe.Zip = "54353"; + joe.Country = "USA"; + joe.Comments = "very demanding"; + joe.Sex = 'M'; + joe.Salesperson = mark; + + Person yomomma = new Person(); + yomomma.Name = "mom"; + yomomma.Sex = 'F'; + + s.Save(yomomma); + s.Save(mark); + s.Save(joe); + + objectsNeedDeleting.Add(yomomma); + objectsNeedDeleting.Add(mark); + objectsNeedDeleting.Add(joe); + + s.Flush(); + s.Clear(); + + Person p = s.Get(yomomma.Id); + Assert.AreEqual(yomomma.Name, p.Name); + Assert.AreEqual(yomomma.Sex, p.Sex); + s.Clear(); + + // Copied from H3. Don't really know what it is testing + //Assert.AreEqual(0, s.CreateQuery("from System.Serializable").List().Count); + + Assert.AreEqual(3, s.CreateQuery("from Person").List().Count); + Assert.AreEqual(1, s.CreateQuery("from Person p where p.class is null").List().Count); + Assert.AreEqual(1, s.CreateQuery("from Person p where p.class = Customer").List().Count); + Assert.AreEqual(1, s.CreateQuery("from Customer c").List().Count); + s.Clear(); + + IList customers = s.CreateQuery("from Customer c left join fetch c.Salesperson").List(); + foreach (Customer c in customers) + { + Assert.IsTrue(NHibernateUtil.IsInitialized(c.Salesperson)); + Assert.AreEqual("Mark", c.Salesperson.Name); + } + Assert.AreEqual(1, customers.Count); + s.Clear(); + + mark = (Employee)s.Get(typeof(Employee), mark.Id); + joe = (Customer)s.Get(typeof(Customer), joe.Id); + + mark.Zip = "30306"; + s.Flush(); + s.Clear(); + Assert.AreEqual(1, s.CreateQuery("from Person p where p.Zip = '30306'").List().Count); + + // Clean up done in TearDown + } + + [Test] + [Ignore] + public void TestSequentialSelectsOptionalData() + { + // The "optional" attribute on does not yet work + + User jesus = new User(); + jesus.Name = "Jesus Olvera y Martinez"; + jesus.Sex = 'M'; + + s.Save(jesus); + //objectsNeedDeleting.Add(jesus); + + Assert.AreEqual(1, s.CreateQuery("from Person").List().Count); + Assert.AreEqual(0, s.CreateQuery("from Person p where p.class is null").List().Count); + Assert.AreEqual(1, s.CreateQuery("from Person p where p.class = User").List().Count); + Assert.AreEqual(1, s.CreateQuery("from User u").List().Count); + s.Clear(); + + // Remove the optional row from the join table and requery the User obj + IDbCommand cmd = s.Connection.CreateCommand(); + cmd.CommandText = "delete from t_user"; + cmd.CommandType = CommandType.Text; + cmd.ExecuteNonQuery(); + s.Clear(); + + // Clean up the test data + s.Delete(jesus); + s.Flush(); + + Assert.AreEqual(0, s.CreateQuery("from Person").List().Count); + } + + private Person CreatePerson(string name) + { + Person p = new Person(); + p.Name = name; + p.Sex = 'M'; + p.Address = "123 Some Street"; + p.Zip = "12345"; + p.Country = "Canada"; + p.HomePhone = "555-1234"; + p.BusinessPhone = "555-4321"; + + return p; + } + + protected bool PersonsAreEqual(Person x, Person y) + { + if (!string.Equals(x.Name, y.Name)) return false; + if (x.Sex != y.Sex) return false; + if (!string.Equals(x.Address, y.Address)) return false; + if (!string.Equals(x.Zip, y.Zip)) return false; + if (!string.Equals(x.Country, y.Country)) return false; + if (!string.Equals(x.HomePhone, y.HomePhone)) return false; + if (!string.Equals(x.BusinessPhone, y.BusinessPhone)) return false; + + return true; + } + + private Person[] CreateAndInsertPersons(int count) + { + Person[] result = new Person[count]; + + for (int i = 0; i < count; i++) + { + result[i] = CreatePerson("Person " + i.ToString()); + s.Save(result[i]); + objectsNeedDeleting.Add(result[i]); + } + + s.Flush(); + s.Clear(); + + return result; + } + + [Test] + public void TestRetrieveUsingGet() + { + // Create a new person John + Person john = CreatePerson("John"); + + s.Save(john); + objectsNeedDeleting.Add(john); + s.Flush(); + s.Clear(); + + Person p = (Person)s.Get(typeof(Person), john.Id); + Assert.IsTrue(PersonsAreEqual(john, p)); + } + + [Test] + public void TestRetrieveUsingCriteriaInterface() + { + Person[] people = CreateAndInsertPersons(3); + + ICriteria criteria = s.CreateCriteria(typeof(Person)) + .Add(Expression.Expression.Eq("Name", people[1].Name)); + IList list = criteria.List(); + + Assert.AreEqual(1, list.Count); + Assert.IsTrue(PersonsAreEqual(people[1], (Person)list[0])); + } + + [Test] + public void TestRetrieveUsingHql() + { + Person[] people = CreateAndInsertPersons(3); + + IQuery query = s.CreateQuery("from Person p where p.Name = :name") + .SetParameter("name", people[1].Name); + IList list = query.List(); + + Assert.AreEqual(1, list.Count); + Assert.IsTrue(PersonsAreEqual(people[1], (Person)list[0])); + } + + private Employee CreateEmployee(string name, string title) + { + Employee p = new Employee(); + p.Name = name; + p.Sex = 'M'; + p.Address = "123 Some Street"; + p.Zip = "12345"; + p.Country = "Canada"; + p.HomePhone = "555-1234"; + p.BusinessPhone = "555-4321"; + + p.Title = title; + p.Salary = 100; + + return p; + } + + private Employee[] CreateAndInsertEmployees(int count) + { + Employee[] result = new Employee[count]; + + for (int i = 0; i < count; i++) + { + result[i] = CreateEmployee("Employee " + i.ToString(), "Title " + i.ToString()); + s.Save(result[i]); + objectsNeedDeleting.Add(result[i]); + } + + s.Flush(); + s.Clear(); + + return result; + } + + + [Test] + public void TestSimpleInsertAndRetrieveEmployee() + { + // Create a new employee Jack + Employee jack = CreateEmployee("Jack", "Boss"); + + s.Save(jack); + objectsNeedDeleting.Add(jack); + s.Flush(); + s.Clear(); + + IList list = s.CreateQuery("from Person p where p.Id = :id") + .SetParameter("id", jack.Id) + .List(); + } + + [Test] + public void TestDeleteUsingHql() + { + Person[] people = new Person[3]; + for (int i = 0; i < people.Length; i++) + { + people[i] = CreatePerson(string.Format("Person {0}", i + 1)); + s.Save(people[i]); + objectsNeedDeleting.Add(people[i]); + } + + s.Flush(); + s.Clear(); + + s.Delete("from Person"); + s.Flush(); + + IList list = s.CreateQuery("from Person").List(); + objectsNeedDeleting = list; + Assert.AreEqual(0, list.Count); + } + + private bool EmployeesAreEqual(Employee x, Employee y) + { + if (!PersonsAreEqual(x, y)) return false; + if (!string.Equals(x.Title, y.Title)) return false; + if (x.Salary != y.Salary) return false; + + if (x.Manager != null && y.Manager != null) + { + return x.Manager.Id == y.Manager.Id; + } + else if (x.Manager != null || y.Manager != null) + { + return false; + } + else // x.Manager and y.Manager are both null + { + return true; + } + } + + [Test] + public void TestUpdateEmployee() + { + Employee[] employees = CreateAndInsertEmployees(3); + + Employee emp0 = (Employee)s.Get(typeof(Employee), employees[0].Id); + Assert.IsNotNull(emp0); + emp0.Address = "Address"; + emp0.BusinessPhone = "BusinessPhone"; + emp0.Country = "Country"; + emp0.HomePhone = "HomePhone"; + emp0.Manager = employees[2]; + emp0.Name = "Name"; + emp0.Salary = 20000; + emp0.Title = "Title"; + emp0.Zip = "Zip"; + // Not updating emp0.Sex because it is marked update=false in the mapping file. + + s.Flush(); + s.Clear(); + + Employee emp0updated = (Employee)s.Get(typeof(Employee), employees[0].Id); + Assert.IsTrue(EmployeesAreEqual(emp0, emp0updated)); + } + + [Test] + public void Learn_SubclassBehavior() + { + SubclassOne one = new SubclassOne(); + one.TestDateTime = DateTime.Now; + + s.Save(one); + s.Flush(); + s.Clear(); + + SubclassOne result = (SubclassOne)s.Get(typeof(SubclassBase), one.Id); + Assert.IsNotNull(result); + Assert.IsTrue(result is SubclassOne); + + s.Delete(result); + } + + [Test] + public void PolymorphicGetByTypeofSuperclass() + { + Employee[] employees = CreateAndInsertEmployees(1); + Employee emp0 = (Employee)s.Get(typeof(Person), employees[0].Id); + Assert.IsNotNull(emp0); + Assert.IsTrue(emp0 is Employee); + } + } +} Index: src/NHibernate.Test/Join/Person.cs =================================================================== --- src/NHibernate.Test/Join/Person.cs (revision 0) +++ src/NHibernate.Test/Join/Person.cs (revision 0) @@ -0,0 +1,63 @@ +using System; + +namespace NHibernate.Test.Join +{ + public class Person + { + private char _Sex; + public virtual char Sex + { + get { return _Sex; } + set { _Sex = value; } + } + + private long _Id; + public virtual long Id + { + get { return _Id; } + set { _Id = value; } + } + + private string _Name; + public virtual string Name + { + get { return _Name; } + set { _Name = value; } + } + + private string _Country; + public virtual string Country + { + get { return _Country; } + set { _Country = value; } + } + + private string _Zip; + public virtual string Zip + { + get { return _Zip; } + set { _Zip = value; } + } + + private string _Address; + public virtual string Address + { + get { return _Address; } + set { _Address = value; } + } + + private string _HomePhone; + public virtual string HomePhone + { + get { return _HomePhone; } + set { _HomePhone = value; } + } + + private string _BusinessPhone; + public virtual string BusinessPhone + { + get { return _BusinessPhone; } + set { _BusinessPhone = value; } + } + } +} Index: src/NHibernate.Test/Join/Person.hbm.xml =================================================================== --- src/NHibernate.Test/Join/Person.hbm.xml (revision 0) +++ src/NHibernate.Test/Join/Person.hbm.xml (revision 0) @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file Index: src/NHibernate.Test/Join/Simple.cs =================================================================== Index: src/NHibernate.Test/Join/User.cs =================================================================== --- src/NHibernate.Test/Join/User.cs (revision 0) +++ src/NHibernate.Test/Join/User.cs (revision 0) @@ -0,0 +1,17 @@ +using System; + +namespace NHibernate.Test.Join +{ + public class User : Person + { + private string _Silly; + + private string _Login; + public virtual string Login + { + get { return _Login; } + set { _Login = value; } + } + + } +} Index: src/NHibernate.Test/NHibernate.Test-2.0.csproj =================================================================== --- src/NHibernate.Test/NHibernate.Test-2.0.csproj (revision 2594) +++ src/NHibernate.Test/NHibernate.Test-2.0.csproj (working copy) @@ -171,6 +171,11 @@ + + + + + @@ -779,6 +784,9 @@ + + + Index: src/NHibernate/Cfg/HbmBinder.cs =================================================================== --- src/NHibernate/Cfg/HbmBinder.cs (revision 2594) +++ src/NHibernate/Cfg/HbmBinder.cs (working copy) @@ -241,8 +241,6 @@ model.RootClazz.ClassPersisterClass = typeof (SingleTableEntityPersister); } - model.Table = model.Superclass.Table; - log.Info("Mapping subclass: " + model.Name + " -> " + model.Table.Name); // properties @@ -262,7 +260,7 @@ } } - public static void BindJoinedSubclass(XmlNode node, Subclass model, Mappings mappings) + public static void BindJoinedSubclass(XmlNode node, JoinedSubclass model, Mappings mappings) { BindClass(node, model, mappings); @@ -276,16 +274,16 @@ XmlAttribute schemaNode = node.Attributes["schema"]; string schema = schemaNode == null ? mappings.SchemaName : schemaNode.Value; Table mytable = mappings.AddTable(schema, GetClassTableName(model, node, mappings)); - model.Table = mytable; + ((ITableOwner)model).Table = mytable; log.Info("Mapping joined-subclass: " + model.Name + " -> " + model.Table.Name); XmlNode keyNode = node.SelectSingleNode(HbmConstants.nsKey, nsmgr); - SimpleValue key = new SimpleValue(mytable); + SimpleValue key = new DependentValue(mytable, model.Identifier); model.Key = key; BindSimpleValue(keyNode, key, false, model.Name, mappings); + model.Key.Type = model.Identifier.Type; - model.Key.Type = model.Identifier.Type; model.CreatePrimaryKey(dialect); model.CreateForeignKey(); @@ -308,7 +306,7 @@ XmlAttribute schemaNode = node.Attributes["schema"]; string schema = schemaNode == null ? mappings.SchemaName : schemaNode.Value; Table table = mappings.AddTable(schema, GetClassTableName(model, node, mappings)); - model.Table = table; + ((ITableOwner)model).Table = table; log.Info("Mapping class: " + model.Name + " -> " + model.Table.Name); @@ -471,6 +469,109 @@ PropertiesFromXML(node, model, mappings); } + public static void BindJoin(XmlNode node, Join join, Mappings mappings) + { + PersistentClass persistentClass = join.PersistentClass; + String path = persistentClass.Name; + + // TABLENAME + + XmlAttribute schemaNode = node.Attributes["schema"]; + string schema = schemaNode == null ? + mappings.SchemaName : schemaNode.Value; + + Table primaryTable = persistentClass.Table; + Table table = mappings.AddTable( + schema, + GetClassTableName(persistentClass, node, mappings)); + join.Table = table; + + XmlAttribute fetchNode = node.Attributes["fetch"]; + if (fetchNode != null) + { + join.IsSequentialSelect = "select".Equals(fetchNode.Value); + } + + XmlAttribute invNode = node.Attributes["inverse"]; + if (invNode != null) + { + join.IsInverse = "true".Equals(invNode.Value); + } + + XmlAttribute nullNode = node.Attributes["optional"]; + if (nullNode != null) + { + join.IsOptional = "true".Equals(nullNode.Value); + } + + log.Info( + "Mapping class join: " + persistentClass.Name + + " -> " + join.Table.Name + ); + + // KEY + XmlNode keyNode = node.SelectSingleNode(HbmConstants.nsKey, nsmgr); + SimpleValue key = new DependentValue(table, persistentClass.Identifier); + join.Key = key; + // key.SetCascadeDeleteEnabled("cascade".Equals(keyNode.Attributes["on-delete"].Value)); + BindSimpleValue(keyNode, key, false, persistentClass.Name, mappings); + // TODO: not sure if the following if-block is correct + if (key.Type == null) + key.Type = persistentClass.Identifier.Type; + + join.CreatePrimaryKey(dialect); + join.CreateForeignKey(); + + // PROPERTIES + //PropertiesFromXML(node, persistentClass, mappings); + foreach (XmlNode subnode in node.ChildNodes) + { + string name = subnode.Name; + XmlAttribute nameAttribute = subnode.Attributes["name"]; + string propertyName = nameAttribute == null ? null : nameAttribute.Value; + + IValue value = null; + switch (name) + { + case "many-to-one": + value = new ManyToOne(table); + BindManyToOne(subnode, (ManyToOne)value, propertyName, true, mappings); + break; + case "any": + value = new Any(table); + BindAny(subnode, (Any)value, true, mappings); + break; + case "property": + value = new SimpleValue(table); + BindSimpleValue(subnode, (SimpleValue)value, true, propertyName, mappings); + break; + case "component": + case "dynamic-component": + string subpath = StringHelper.Qualify(path, propertyName); + value = new Component(join); + BindComponent( + subnode, + (Component)value, + join.PersistentClass.MappedClass, + propertyName, + subpath, + true, + mappings); + break; + } + + if (value != null) + { + NHibernate.Mapping.Property prop = CreateProperty(value, propertyName, persistentClass.MappedClass, subnode, mappings); + prop.IsOptional = join.IsOptional; + join.AddProperty(prop); + } + } + + // CUSTOM SQL + HandleCustomSQL(node, join); + } + public static void BindColumns(XmlNode node, SimpleValue model, bool isNullable, bool autoColumn, string propertyPath, Mappings mappings) { @@ -548,18 +649,9 @@ public static void BindSimpleValue(XmlNode node, SimpleValue model, bool isNullable, string path, Mappings mappings) { model.Type = GetTypeFromXML(node); + //BindSimpleValueType(node, model, mappings); - XmlAttribute formulaNode = node.Attributes["formula"]; - if (formulaNode != null) - { - Formula f = new Formula(); - f.FormulaString = formulaNode.InnerText; - model.AddFormula(f); - } - else - { - BindColumns(node, model, isNullable, true, path, mappings); - } + BindColumnsOrFormula(node, model, path, isNullable, mappings); XmlAttribute fkNode = node.Attributes["foreign-key"]; if (fkNode != null) @@ -568,6 +660,33 @@ } } + private static void BindSimpleValueType(XmlNode node, SimpleValue simpleValue, Mappings mappings) + { + string typeName = null; + + System.Collections.Specialized.StringDictionary parameters = new System.Collections.Specialized.StringDictionary(); + + XmlAttribute typeNode = node.Attributes["type"]; + if (typeNode == null) typeNode = node.Attributes["id-type"]; // for an any + if (typeNode != null) typeName = typeNode.Value; + + //XmlNode typeChild = node.SelectSingleNode("type"); + //if (typeName == null && typeChild != null) + //{ + // typeName = typeChild.Attributes["name"].Value; + // foreach (XmlNode paramElement in typeChild.SelectNodes("param")) + // { + // parameters[paramElement.Attributes["name"].Value] = paramElement.InnerText.Trim(); + // } + //} + + // TODO: Add handling of TypeDefs here when TypeDef is implemented + + //if (parameters.Count == 0) simpleValue + + //if (typeName != null) simpleValue. + } + private static string PropertyAccess(XmlNode node, Mappings mappings) { XmlAttribute accessNode = node.Attributes["access"]; @@ -896,6 +1015,21 @@ //return TypeNameParser.Parse(unqualifiedName, model.DefaultNamespace, model.DefaultAssembly).ToString(); } + private static void BindColumnsOrFormula(XmlNode node, SimpleValue simpleValue, string path, bool isNullable, Mappings mappings) + { + XmlAttribute formulaNode = node.Attributes["formula"]; + if (formulaNode != null) + { + Formula f = new Formula(); + f.FormulaString = formulaNode.InnerText; + simpleValue.AddFormula(f); + } + else + { + BindColumns(node, simpleValue, isNullable, true, path, mappings); + } + } + public static void BindManyToOne(XmlNode node, ManyToOne model, string defaultColumnName, bool isNullable, Mappings mappings) { @@ -1252,14 +1386,15 @@ internal static IType GetTypeFromXML(XmlNode node) { IType type; + + IDictionary parameters = null; + XmlAttribute typeAttribute = node.Attributes["type"]; - if (typeAttribute == null) { typeAttribute = node.Attributes["id-type"]; //for an any } string typeName = null; - IDictionary parameters = null; if (typeAttribute != null) { typeName = typeAttribute.Value; @@ -1480,6 +1615,13 @@ value = new Component(model); BindComponent(subnode, (Component) value, reflectedClass, model.Name, propertyName, true, mappings); } + else if ("join".Equals(name)) + { + Join join = new Join(); + join.PersistentClass = model; + BindJoin(subnode, join, mappings); + model.AddJoin(join); + } else if ("subclass".Equals(name)) { HandleSubclass(model, mappings, subnode); @@ -1528,7 +1670,7 @@ private static void HandleJoinedSubclass(PersistentClass model, Mappings mappings, XmlNode subnode) { - Subclass subclass = new Subclass(model); + JoinedSubclass subclass = new JoinedSubclass(model); BindJoinedSubclass(subnode, subclass, mappings); model.AddSubclass(subclass); mappings.AddClass(subclass); @@ -1536,7 +1678,7 @@ private static void HandleSubclass(PersistentClass model, Mappings mappings, XmlNode subnode) { - Subclass subclass = new Subclass(model); + Subclass subclass = new SingleTableSubclass(model); BindSubclass(subnode, subclass, mappings); model.AddSubclass(subclass); mappings.AddClass(subclass); @@ -1713,7 +1855,7 @@ { model.Element.CreateForeignKey(); } - + model.Key.CreateForeignKeyOfClass(model.Owner.MappedClass); } } @@ -2292,30 +2434,29 @@ } } - // TODO: uncomment when Join implemented - //private static void handleCustomSQL(Element node, Join model) - //{ - // Element element = node.element("sql-insert"); - // if (element != null) - // { - // boolean callable = isCallable(element); - // model.setCustomSQLInsert(element.getTextTrim(), callable, getResultCheckStyle(element, callable)); - // } + private static void HandleCustomSQL(XmlNode node, Join model) + { + XmlNode element = node.SelectSingleNode(HbmConstants.nsSqlInsert, nsmgr); + if (element != null) + { + bool callable = IsCallable(element); + model.SetCustomSQLInsert(element.InnerText.Trim(), callable, GetResultCheckStyle(element, callable)); + } - // element = node.element("sql-delete"); - // if (element != null) - // { - // boolean callable = isCallable(element); - // model.setCustomSQLDelete(element.getTextTrim(), callable, getResultCheckStyle(element, callable)); - // } + element = node.SelectSingleNode(HbmConstants.nsSqlDelete, nsmgr); + if (element != null) + { + bool callable = IsCallable(element); + model.SetCustomSQLDelete(element.InnerText.Trim(), callable, GetResultCheckStyle(element, callable)); + } - // element = node.element("sql-update"); - // if (element != null) - // { - // boolean callable = isCallable(element); - // model.setCustomSQLUpdate(element.getTextTrim(), callable, getResultCheckStyle(element, callable)); - // } - //} + element = node.SelectSingleNode(HbmConstants.nsSqlUpdate, nsmgr); + if (element != null) + { + bool callable = IsCallable(element); + model.SetCustomSQLUpdate(element.InnerText.Trim(), callable, GetResultCheckStyle(element, callable)); + } + } private static void HandleCustomSQL(XmlNode node, Mapping.Collection model) { Index: src/NHibernate/Impl/SessionFactoryImpl.cs =================================================================== --- src/NHibernate/Impl/SessionFactoryImpl.cs (revision 2594) +++ src/NHibernate/Impl/SessionFactoryImpl.cs (working copy) @@ -169,7 +169,6 @@ } } - // Persisters: IDictionary caches = new Hashtable(); Index: src/NHibernate/Loader/Loader.cs =================================================================== --- src/NHibernate/Loader/Loader.cs (revision 2594) +++ src/NHibernate/Loader/Loader.cs (working copy) @@ -1023,13 +1023,13 @@ EntityAliases[ i ].SuffixedPropertyAliases : EntityAliases[ i ].GetSuffixedPropertyAliases( persister ); - object[ ] values = Hydrate( + object[] values = persister.Hydrate( rs, id, obj, - persister, - session, - cols ); + rootPersister, + cols, + session); // TODO H3: // IAssociationType[] ownerAssociationTypes = OwnerAssociationTypes; @@ -1096,40 +1096,6 @@ } /// - /// Unmarshall the fields of a persistent instance from a result set, - /// without resolving associations or collections - /// - /// - /// - /// - /// - /// - /// - /// - private object[ ] Hydrate( - IDataReader rs, - object id, - object obj, - ILoadable persister, - ISessionImplementor session, - string[ ][ ] suffixedPropertyColumns ) - { - if( log.IsDebugEnabled ) - { - log.Debug( "Hydrating entity: " + persister.ClassName + '#' + id ); - } - - IType[ ] types = persister.PropertyTypes; - object[ ] values = new object[types.Length]; - - for( int i = 0; i < types.Length; i++ ) - { - values[ i ] = types[ i ].Hydrate( rs, suffixedPropertyColumns[ i ], session, obj ); - } - return values; - } - - /// /// Advance the cursor to the first required row of the IDataReader /// /// Index: src/NHibernate/Mapping/Component.cs =================================================================== --- src/NHibernate/Mapping/Component.cs (revision 2594) +++ src/NHibernate/Mapping/Component.cs (working copy) @@ -94,6 +94,12 @@ this.owner = null; } + public Component(Join join) + : base(join.Table) + { + this.owner = join.PersistentClass; + } + /// /// /// Index: src/NHibernate/Mapping/Constraint.cs =================================================================== --- src/NHibernate/Mapping/Constraint.cs (revision 2594) +++ src/NHibernate/Mapping/Constraint.cs (working copy) @@ -46,6 +46,18 @@ } } + public void AddColumns(IEnumerator columnIterator) + { + while (columnIterator.MoveNext()) + { + ISelectable col = (ISelectable)columnIterator.Current; + if (!col.IsFormula) + { + AddColumn((Column)col); + } + } + } + /// /// Gets the number of columns that this Constraint contains. /// Index: src/NHibernate/Mapping/DependentValue.cs =================================================================== --- src/NHibernate/Mapping/DependentValue.cs (revision 0) +++ src/NHibernate/Mapping/DependentValue.cs (revision 0) @@ -0,0 +1,46 @@ +using NHibernate.Type; +using System; + +namespace NHibernate.Mapping +{ + public class DependentValue : SimpleValue + { + private IKeyValue wrappedValue; + private bool isNullable; + private bool isUpdateable; + + public DependentValue(Table table, IKeyValue prototype) + : base(table) + { + this.wrappedValue = prototype; + } + + public override IType Type + { + get { return wrappedValue.Type; } + } + + public void SetTypeUsingReflection(string className, string propertyName) { } + + public override bool IsNullable + { + get { return isNullable; } + } + + // SimpleValue does not have a setter for IsNullable. We cannot add + // a setter here with the "override" keyword on the IsNullable property. + // Therefore, we need to create this method to set IsNullable. + // This is a limitation on .NET 2.0 and before. + public void SetNullable(bool nullable) + { + isNullable = nullable; + } + + public virtual bool IsUpdateable + { + get { return isUpdateable; } + set { isUpdateable = value; } + } + + } +} Index: src/NHibernate/Mapping/IKeyValue.cs =================================================================== --- src/NHibernate/Mapping/IKeyValue.cs (revision 2594) +++ src/NHibernate/Mapping/IKeyValue.cs (working copy) @@ -12,8 +12,7 @@ /// public interface IKeyValue : IValue { - // TODO H3: - //void CreateForeignKeyOfEntity( string entityName ); + void CreateForeignKeyOfClass(System.Type persistentClass); // TODO H3: //bool IsCascadeDeleteEnabled { get; } Index: src/NHibernate/Mapping/ITableOwner.cs =================================================================== --- src/NHibernate/Mapping/ITableOwner.cs (revision 0) +++ src/NHibernate/Mapping/ITableOwner.cs (revision 0) @@ -0,0 +1,9 @@ +using System; + +namespace NHibernate.Mapping +{ + public interface ITableOwner + { + Table Table { set; } + } +} Index: src/NHibernate/Mapping/Join.cs =================================================================== --- src/NHibernate/Mapping/Join.cs (revision 0) +++ src/NHibernate/Mapping/Join.cs (revision 0) @@ -0,0 +1,139 @@ +using System; +using System.Collections; + +using NHibernate.Engine; +using NHibernate.SqlCommand; + +namespace NHibernate.Mapping +{ + [Serializable] + public class Join + { + private static readonly Alias PK_ALIAS = new Alias(15, "PK"); + + private ArrayList properties = new ArrayList(); + private Table table; + private IKeyValue key; + private PersistentClass persistentClass; + private bool isSequentialSelect; + private bool isInverse; + private bool isOptional; + + // Custom SQL + private SqlString customSQLInsert; + private bool customInsertCallable; + private ExecuteUpdateResultCheckStyle insertCheckStyle; + private SqlString customSQLUpdate; + private bool customUpdateCallable; + private ExecuteUpdateResultCheckStyle updateCheckStyle; + private SqlString customSQLDelete; + private bool customDeleteCallable; + private ExecuteUpdateResultCheckStyle deleteCheckStyle; + + public void AddProperty(Property prop) + { + properties.Add(prop); + prop.PersistentClass = this.PersistentClass; + } + + public bool ContainsProperty(Property prop) + { + return properties.Contains(prop); + } + + public ICollection PropertyCollection + { + get { return properties; } + } + + public virtual Table Table + { + get { return table; } + set { table = value; } + } + + public virtual IKeyValue Key + { + get { return key; } + set { key = value; } + } + + public virtual PersistentClass PersistentClass + { + get { return persistentClass; } + set { persistentClass = value; } + } + + public void CreateForeignKey() + { + Key.CreateForeignKeyOfClass(persistentClass.MappedClass); + } + + public void CreatePrimaryKey(Dialect.Dialect dialect) + { + //Primary key constraint + PrimaryKey pk = new PrimaryKey(); + pk.Table = table; + pk.Name = PK_ALIAS.ToAliasString(table.Name, dialect); + table.PrimaryKey = pk; + + pk.AddColumns(Key.ColumnCollection.GetEnumerator()); + } + + public int PropertySpan + { + get { return properties.Count; } + } + + public SqlString CustomSQLInsert { get { return customSQLInsert; } } + public SqlString CustomSQLDelete { get { return customSQLDelete; } } + public SqlString CustomSQLUpdate { get { return customSQLUpdate; } } + + public bool IsCustomInsertCallable { get { return customInsertCallable; } } + public bool IsCustomDeleteCallable { get { return customDeleteCallable; } } + public bool IsCustomUpdateCallable { get { return customUpdateCallable; } } + + public ExecuteUpdateResultCheckStyle CustomSQLInsertCheckStyle { get { return insertCheckStyle; } } + public ExecuteUpdateResultCheckStyle CustomSQLDeleteCheckStyle { get { return deleteCheckStyle; } } + public ExecuteUpdateResultCheckStyle CustomSQLUpdateCheckStyle { get { return updateCheckStyle; } } + + public void SetCustomSQLInsert(string sql, bool callable, ExecuteUpdateResultCheckStyle checkStyle) + { + customSQLInsert = SqlString.Parse(sql); + customInsertCallable = callable; + insertCheckStyle = checkStyle; + } + + public void SetCustomSQLDelete(string sql, bool callable, ExecuteUpdateResultCheckStyle checkStyle) + { + customSQLDelete = SqlString.Parse(sql); + customDeleteCallable = callable; + deleteCheckStyle = checkStyle; + } + + public void SetCustomSQLUpdate(string sql, bool callable, ExecuteUpdateResultCheckStyle checkStyle) + { + customSQLUpdate = SqlString.Parse(sql); + customUpdateCallable = callable; + updateCheckStyle = checkStyle; + } + + public virtual bool IsSequentialSelect + { + get { return isSequentialSelect; } + set { isSequentialSelect = value; } + } + + public virtual bool IsInverse + { + get { return isInverse; } + set { isInverse = value; } + } + + public virtual bool IsOptional + { + get { return isOptional; } + set { isOptional = value; } + } + } +} Index: src/NHibernate/Mapping/JoinedSubclass.cs =================================================================== --- src/NHibernate/Mapping/JoinedSubclass.cs (revision 0) +++ src/NHibernate/Mapping/JoinedSubclass.cs (revision 0) @@ -0,0 +1,49 @@ +using System; +using System.Text; + +using NHibernate.Engine; + +namespace NHibernate.Mapping +{ + public class JoinedSubclass : Subclass, ITableOwner + { + private Table table; + private SimpleValue key; + + public JoinedSubclass(PersistentClass superclass) + : base(superclass) { } + + public override Table Table + { + get { return table; } + } + + Table ITableOwner.Table + { + set + { + table = value; + Superclass.AddSubclassTable(table); + } + } + + public override SimpleValue Key + { + get { return key; } + set { key = value; } + } + + /// + /// + /// + /// + public override void Validate(IMapping mapping) + { + base.Validate(mapping); + if (Key != null && !Key.IsValid(mapping)) + { + throw new MappingException(string.Format("subclass key has wrong number of columns: {0} type: {1}", MappedClass.Name, Key.Type.Name)); + } + } + } +} Index: src/NHibernate/Mapping/PersistentClass.cs =================================================================== --- src/NHibernate/Mapping/PersistentClass.cs (revision 2594) +++ src/NHibernate/Mapping/PersistentClass.cs (working copy) @@ -26,7 +26,6 @@ private string discriminatorValue; private bool lazy; private ArrayList properties = new ArrayList(); - private Table table; private System.Type proxyInterface; private readonly ArrayList subclasses = new ArrayList(); private readonly ArrayList subclassProperties = new ArrayList(); @@ -37,6 +36,8 @@ private bool selectBeforeUpdate; private OptimisticLockMode optimisticLockMode; private IDictionary metaAttributes; + private readonly ArrayList joins = new ArrayList(); + private readonly ArrayList subclassJoins = new ArrayList(); private IDictionary filters = new Hashtable(); @@ -203,16 +204,31 @@ /// /// The value of this is set by the table attribute. /// - public virtual Table Table + public abstract Table Table { get; } + + public virtual int PropertyClosureSpan { - get { return table; } - set { table = value; } + get + { + int span = properties.Count; + foreach (Join join in joins) + { + span += join.PropertySpan; + } + return span; + } } - public virtual int PropertyClosureSpan + public virtual int GetJoinNumber(Property prop) { - get { return properties.Count; } - // TODO H3: add properties of s. + int result = 1; + foreach (Join join in SubclassJoinClosureCollection) + { + if (join.ContainsProperty(prop)) + return result; + result++; + } + return 0; } /// @@ -223,7 +239,16 @@ /// public virtual ICollection PropertyCollection { - get { return properties; } + get + { + ArrayList result = new ArrayList(); + result.AddRange(properties); + foreach (Join join in joins) + { + result.AddRange(join.PropertyCollection); + } + return result; + } } /// @@ -388,6 +413,11 @@ subclassProperties.Add( p ); } + public virtual void AddSubclassJoin(Join join) + { + subclassJoins.Add(join); + } + /// /// Adds a that a subclass is stored in. /// @@ -412,10 +442,25 @@ ArrayList retVal = new ArrayList(); retVal.AddRange( PropertyClosureCollection ); retVal.AddRange( subclassProperties ); + foreach (Join join in subclassJoins) + { + retVal.AddRange(join.PropertyCollection); + } return retVal; } } + public virtual ICollection SubclassJoinClosureCollection + { + get + { + ArrayList retVal = new ArrayList(); + retVal.AddRange(JoinClosureCollection); + retVal.AddRange(subclassJoins); + return retVal; + } + } + /// /// Gets an of all of the objects that the /// subclass finds its information in. @@ -433,6 +478,16 @@ } } + public virtual bool IsClassOrSuperclassJoin(Join join) + { + return joins.Contains(join); + } + + public virtual bool IsClassOrSuperclassTable(Table closureTable) + { + return Table == closureTable; + } + /// /// Gets or sets the to use as a Proxy. /// @@ -487,6 +542,27 @@ return ( MetaAttribute ) metaAttributes[ name ]; } + public virtual ICollection JoinCollection + { + get { return joins; } + } + + public virtual ICollection JoinClosureCollection + { + get { return joins; } + } + + public virtual void AddJoin(Join join) + { + joins.Add(join); + join.PersistentClass = this; + } + + public virtual int JoinClosureSpan + { + get { return joins.Count; } + } + public bool IsLazy { get { return lazy; } @@ -543,6 +619,7 @@ public virtual void CreatePrimaryKey( Dialect.Dialect dialect ) { PrimaryKey pk = new PrimaryKey(); + Table table = Table; pk.Table = table; pk.Name = PKAlias.ToAliasString( table.Name, dialect ); table.PrimaryKey = pk; Index: src/NHibernate/Mapping/Property.cs =================================================================== --- src/NHibernate/Mapping/Property.cs (revision 2594) +++ src/NHibernate/Mapping/Property.cs (working copy) @@ -18,7 +18,9 @@ private string cascade; private bool updateable = true; private bool insertable = true; + private bool selectable = true; private string propertyAccessorName; + private bool optional; private IDictionary metaAttributes; private PersistentClass persistentClass; private bool isOptimisticLocked; @@ -180,7 +182,8 @@ public bool IsOptional { - get { return IsNullable; } + get { return this.optional || IsNullable; } + set { this.optional = value; } } /// @@ -265,6 +268,12 @@ set { persistentClass = value; } } + public bool IsSelectable + { + get { return selectable; } + set { selectable = value; } + } + public bool IsOptimisticLocked { get { return isOptimisticLocked; } Index: src/NHibernate/Mapping/RootClass.cs =================================================================== --- src/NHibernate/Mapping/RootClass.cs (revision 2594) +++ src/NHibernate/Mapping/RootClass.cs (working copy) @@ -11,7 +11,7 @@ /// is the root class of a table-per-sublcass, or table-per-concrete-class /// inheritance heirarchy. /// - public class RootClass : PersistentClass + public class RootClass : PersistentClass, ITableOwner { private static readonly ILog log = LogManager.GetLogger( typeof( RootClass ) ); @@ -40,6 +40,7 @@ private System.Type classPersisterClass; private bool forceDiscriminator; private string where; + private Table table; private bool discriminatorInsertable = true; private int nextSubclassId = 0; @@ -53,6 +54,16 @@ get { return 0; } } + public override Table Table + { + get { return table; } + } + + Table ITableOwner.Table + { + set { table = value; } + } + /// /// Gets or sets the that is used as the id. /// Index: src/NHibernate/Mapping/SimpleValue.cs =================================================================== --- src/NHibernate/Mapping/SimpleValue.cs (revision 2594) +++ src/NHibernate/Mapping/SimpleValue.cs (working copy) @@ -71,11 +71,11 @@ this.type = value; int count = 0; - foreach( ISelectable sel in ColumnCollection ) + foreach (ISelectable sel in ColumnCollection) { if (sel is Column) { - Column col = (Column) sel; + Column col = (Column)sel; col.Type = type; col.TypeIndex = count++; } Index: src/NHibernate/Mapping/SingleTableSubclass.cs =================================================================== --- src/NHibernate/Mapping/SingleTableSubclass.cs (revision 0) +++ src/NHibernate/Mapping/SingleTableSubclass.cs (revision 0) @@ -0,0 +1,20 @@ +using System; +using System.Text; + +namespace NHibernate.Mapping +{ + public class SingleTableSubclass : Subclass + { + public SingleTableSubclass(PersistentClass superclass) + : base(superclass) { } + + public override void Validate(NHibernate.Engine.IMapping mapping) + { + if (Discriminator == null) + { + throw new MappingException(string.Format("No discriminator found for {0}. Discriminator is needed when 'single-table-per-hierarchy' is used and a class has subclasses", MappedClass.Name)); + } + base.Validate(mapping); + } + } +} Index: src/NHibernate/Mapping/Subclass.cs =================================================================== --- src/NHibernate/Mapping/Subclass.cs (revision 2594) +++ src/NHibernate/Mapping/Subclass.cs (working copy) @@ -190,6 +190,12 @@ Superclass.AddSubclassProperty(p); } + public override void AddJoin(Join join) + { + base.AddJoin(join); + Superclass.AddSubclassJoin(join); + } + /// /// Gets or Sets the that this class is stored in. /// @@ -200,12 +206,7 @@ /// public override Table Table { - get { return base.Table; } - set - { - base.Table = value; - Superclass.AddSubclassTable(value); - } + get { return Superclass.Table; } } /// @@ -267,6 +268,12 @@ Superclass.AddSubclassProperty(p); } + public override void AddSubclassJoin(Join join) + { + base.AddSubclassJoin(join); + Superclass.AddSubclassJoin(join); + } + /// /// Adds a that a subclass is stored in. /// @@ -396,19 +403,6 @@ /// /// /// - /// - public override void Validate(IMapping mapping) - { - base.Validate(mapping); - if (Key != null && !Key.IsValid(mapping)) - { - throw new MappingException(string.Format("subclass key has wrong number of columns: {0} type: {1}", MappedClass.Name, Key.Type.Name)); - } - } - - /// - /// - /// public void CreateForeignKey() { if (!IsJoinedSubclass) @@ -419,11 +413,37 @@ Key.CreateForeignKeyOfClass(Superclass.MappedClass); } + public override int JoinClosureSpan + { + get { return Superclass.JoinClosureSpan + base.JoinClosureSpan; } + } + public override int PropertyClosureSpan { get { return Superclass.PropertyClosureSpan + base.PropertyClosureSpan; } } + public override ICollection JoinClosureCollection + { + get + { + ArrayList result = new ArrayList(); + result.AddRange(Superclass.JoinClosureCollection); + result.AddRange(base.JoinClosureCollection); + return result; + } + } + + public override bool IsClassOrSuperclassJoin(Join join) + { + return base.IsClassOrSuperclassJoin(join) || Superclass.IsClassOrSuperclassJoin(join); + } + + public override bool IsClassOrSuperclassTable(Table closureTable) + { + return base.IsClassOrSuperclassTable(closureTable) || Superclass.IsClassOrSuperclassTable(closureTable); + } + public override ISet SynchronizedTables { get Index: src/NHibernate/NHibernate-2.0.csproj =================================================================== --- src/NHibernate/NHibernate-2.0.csproj (revision 2594) +++ src/NHibernate/NHibernate-2.0.csproj (working copy) @@ -656,11 +656,16 @@ + + + + + @@ -674,6 +679,7 @@ + Index: src/NHibernate/nhibernate-mapping.xsd =================================================================== --- src/NHibernate/nhibernate-mapping.xsd (revision 2594) +++ src/NHibernate/nhibernate-mapping.xsd (working copy) @@ -338,7 +338,10 @@ - + + + + @@ -486,6 +489,7 @@ + @@ -543,6 +547,26 @@ + + + + + + + + + + + + + default: no value + + + + + + + Property of an entity class or component, component-element, composite-id, etc. Class Properties (get_ and set_ methods) are mapped to table columns Index: src/NHibernate/Persister/Entity/AbstractEntityPersister.cs =================================================================== --- src/NHibernate/Persister/Entity/AbstractEntityPersister.cs (revision 2594) +++ src/NHibernate/Persister/Entity/AbstractEntityPersister.cs (working copy) @@ -35,7 +35,7 @@ /// /// May be considered an immutable view of the mapping object /// - public abstract class AbstractEntityPersister : AbstractPropertyMapping, IOuterJoinLoadable, IQueryable, IClassMetadata, + public abstract class AbstractEntityPersister : IOuterJoinLoadable, IQueryable, IClassMetadata, IUniqueKeyLoadable, ISqlLoadable { private readonly ISessionFactoryImplementor factory; @@ -123,25 +123,29 @@ private readonly bool[][] propertyColumnInsertable; private readonly bool[][] propertyColumnUpdateable; private readonly bool[] propertyUniqueness; + private readonly System.Type[] propertySubclassNames; private readonly bool hasFormulaProperties; // the closure of all columns used by the entire hierarchy including // subclasses and superclasses of this class private readonly string[] subclassColumnClosure; + private readonly string[] subclassColumnLazyClosure; + private readonly string[] subclassColumnAliasClosure; + private readonly bool[] subclassColumnSelectableClosure; private readonly string[] subclassFormulaTemplateClosure; private readonly string[] subclassFormulaClosure; - private readonly string[] subclassColumnAliasClosure; private readonly string[] subclassFormulaAliasClosure; // the closure of all properties in the entire hierarchy including // subclasses and superclasses of this class + private readonly string[] subclassPropertyNameClosure; + private readonly System.Type[] subclassPropertySubclassNameClosure; + private readonly IType[] subclassPropertyTypeClosure; private readonly string[][] subclassPropertyFormulaTemplateClosure; private readonly string[][] subclassPropertyColumnNameClosure; + private readonly FetchMode[] subclassPropertyFetchModeClosure; private readonly bool[] subclassPropertyNullabilityClosure; - private readonly string[] subclassPropertyNameClosure; - private readonly IType[] subclassPropertyTypeClosure; - private readonly FetchMode[] subclassPropertyFetchModeClosure; private readonly FilterHelper filterHelper; @@ -176,6 +180,12 @@ private IUniqueEntityLoader queryLoader; private bool hasSubselectLoadableCollections; + protected readonly BasicEntityPropertyMapping propertyMapping; + + public abstract string GetSubclassTableName(int j); + + protected abstract string[] GetSubclassTableKeyColumns(int j); + protected SqlString GetLockString(LockMode lockMode) { return (SqlString) lockers[lockMode]; @@ -186,7 +196,8 @@ get { return mappedClass; } } - public override string ClassName + // TODO: override + public string ClassName { get { return entityMetamodel.Type.FullName; } } @@ -445,7 +456,7 @@ get { return entityMetamodel.IdentifierProperty.Type; } } - public override string[] IdentifierColumnNames + public virtual string[] IdentifierColumnNames { get { return rootTableKeyColumnNames; } } @@ -579,6 +590,11 @@ get { return entityMetamodel.IsMutable; } } + public virtual bool IsAbstract + { + get { return entityMetamodel.IsAbstract; } + } + /// public virtual bool HasCache { @@ -629,35 +645,51 @@ } } - private void InitPropertyPaths(IMapping factory) + private void InitOrdinaryPropertyPaths(IMapping mapping) { for (int i = 0; i < SubclassPropertyNameClosure.Length; i++) { - InitPropertyPaths( + propertyMapping.InitPropertyPaths( SubclassPropertyNameClosure[i], SubclassPropertyTypeClosure[i], SubclassPropertyColumnNameClosure[i], SubclassPropertyFormulaTemplateClosure[i], - factory); + mapping); } + } + private void InitIdentifierPropertyPaths(IMapping mapping) + { string idProp = IdentifierPropertyName; if (idProp != null) { - InitPropertyPaths(idProp, IdentifierType, IdentifierColumnNames, null, factory); + propertyMapping.InitPropertyPaths(idProp, IdentifierType, IdentifierColumnNames, null, mapping); } - if (HasEmbeddedIdentifier) + if (entityMetamodel.IdentifierProperty.IsEmbedded) { - InitPropertyPaths(null, IdentifierType, IdentifierColumnNames, null, factory); + propertyMapping.InitPropertyPaths(null, IdentifierType, IdentifierColumnNames, null, mapping); } - InitPropertyPaths(EntityID, IdentifierType, IdentifierColumnNames, null, factory); + propertyMapping.InitPropertyPaths(EntityID, IdentifierType, IdentifierColumnNames, null, mapping); + } - if (IsPolymorphic) + private void InitDiscriminatorPropertyPath(IMapping mapping) + { + propertyMapping.InitPropertyPaths(EntityClass, + DiscriminatorType, + new string[] { DiscriminatorColumnName }, + new string[] { DiscriminatorFormulaTemplate }, + Factory); + } + + private void InitPropertyPaths(IMapping mapping) + { + InitOrdinaryPropertyPaths(mapping); + // TODO: is calling InitOrdinaryPropertyPaths() twice needed here? + InitOrdinaryPropertyPaths(mapping); // do two passes, for collection property-ref + InitIdentifierPropertyPaths(mapping); + if (entityMetamodel.IsPolymorphic) { - AddPropertyPath(EntityClass, DiscriminatorType, - new string[] { DiscriminatorColumnName }, - new string[ ] { DiscriminatorFormulaTemplate } - ); + InitDiscriminatorPropertyPath(mapping); } } @@ -692,6 +724,8 @@ " must declare a default (no-arg) constructor."); } + propertyMapping = new BasicEntityPropertyMapping(this); + // IDENTIFIER hasEmbeddedIdentifier = persistentClass.HasEmbeddedIdentifier; IValue idValue = persistentClass.Identifier; @@ -770,7 +804,6 @@ setters = new ISetter[hydrateSpan]; string[] setterNames = new string[hydrateSpan]; string[] getterNames = new string[hydrateSpan]; - System.Type[] classes = new System.Type[hydrateSpan]; i = 0; @@ -788,7 +821,6 @@ setters[i] = prop.GetSetter(mappedClass); getterNames[i] = getters[i].PropertyName; setterNames[i] = setters[i].PropertyName; - classes[i] = getters[i].ReturnType; string propertyName = prop.Name; gettersByPropertyName[propertyName] = getters[i]; @@ -805,6 +837,7 @@ propertyColumnUpdateable = new bool[HydrateSpan][]; propertyColumnInsertable = new bool[HydrateSpan][]; propertyUniqueness = new bool[HydrateSpan]; + propertySubclassNames = new System.Type[HydrateSpan]; HashedSet thisClassProperties = new HashedSet(); i = 0; @@ -816,6 +849,7 @@ int span = prop.ColumnSpan; propertyColumnSpans[i] = span; + propertySubclassNames[i] = prop.PersistentClass.MappedClass; string[] colNames = new string[span]; string[] colAliases = new string[span]; string[] templates = new string[span]; @@ -856,22 +890,26 @@ // SUBCLASS PROPERTY CLOSURE ArrayList columns = new ArrayList(); //this.subclassColumnClosure + ArrayList columnsLazy = new ArrayList(); //this.subclassColumnClosure ArrayList aliases = new ArrayList(); ArrayList formulaAliases = new ArrayList(); ArrayList formulaTemplates = new ArrayList(); ArrayList types = new ArrayList(); //this.subclassPropertyTypeClosure ArrayList names = new ArrayList(); //this.subclassPropertyNameClosure + ArrayList classes = new ArrayList(); ArrayList subclassTemplates = new ArrayList(); ArrayList propColumns = new ArrayList(); //this.subclassPropertyColumnNameClosure ArrayList joinedFetchesList = new ArrayList(); //this.subclassPropertyEnableJoinedFetch ArrayList cascades = new ArrayList(); ArrayList definedBySubclass = new ArrayList(); // this.propertyDefinedOnSubclass ArrayList formulas = new ArrayList(); + ArrayList columnSelectables = new ArrayList(); ArrayList propNullables = new ArrayList(); foreach (Mapping.Property prop in persistentClass.SubclassPropertyClosureCollection) { names.Add(prop.Name); + classes.Add(prop.PersistentClass.MappedClass); bool isDefinedBySubclass = !thisClassProperties.Contains(prop); definedBySubclass.Add(isDefinedBySubclass); propNullables.Add(prop.IsOptional || isDefinedBySubclass); //TODO: is this completely correct? @@ -882,6 +920,7 @@ int[] colnos = new int[prop.ColumnSpan]; int[] formnos = new int[prop.ColumnSpan]; int l = 0; + // TODO H3: bool lazy = prop.IsLazy; foreach (ISelectable thing in prop.ColumnCollection) { @@ -905,7 +944,7 @@ cols[l] = colName; aliases.Add(thing.GetAlias(factory.Dialect, prop.Value.Table)); // TODO H3: columnsLazy.add( lazy ); - // TODO H3: columnSelectables.add( new Boolean( prop.isSelectable() ) ); + columnSelectables.Add( prop.IsSelectable ); } l++; } @@ -920,15 +959,23 @@ } subclassColumnClosure = (string[]) columns.ToArray(typeof (string)); - subclassFormulaClosure = (string[]) formulas.ToArray(typeof (string)); - subclassFormulaTemplateClosure = (string[]) formulaTemplates.ToArray(typeof (string)); - subclassPropertyTypeClosure = (IType[]) types.ToArray(typeof (IType)); - subclassColumnAliasClosure = (string[]) aliases.ToArray(typeof (string)); - subclassFormulaAliasClosure = (string[]) formulaAliases.ToArray(typeof (string)); - subclassPropertyNameClosure = (string[]) names.ToArray(typeof (string)); + subclassColumnAliasClosure = (string[])aliases.ToArray(typeof(string)); + // TODO H3: subclassColumnLazyClosure = ArrayHelper.ToBooleanArray(columnsLazy); + subclassColumnSelectableClosure = ArrayHelper.ToBooleanArray(columnSelectables); + + subclassFormulaClosure = (string[])formulas.ToArray(typeof(string)); + subclassFormulaTemplateClosure = (string[])formulaTemplates.ToArray(typeof(string)); + subclassFormulaAliasClosure = (string[])formulaAliases.ToArray(typeof(string)); + //subclassFormulaLazyClosure = ArrayHelper.ToBooleanArray(formulasLazy); + + subclassPropertyNameClosure = (string[])names.ToArray(typeof(string)); + subclassPropertySubclassNameClosure = (System.Type[])classes.ToArray(typeof(System.Type)); + subclassPropertyTypeClosure = (IType[])types.ToArray(typeof(IType)); subclassPropertyNullabilityClosure = (bool[]) propNullables.ToArray(typeof (bool)); subclassPropertyFormulaTemplateClosure = ArrayHelper.To2DStringArray(subclassTemplates); subclassPropertyColumnNameClosure = (string[][]) propColumns.ToArray(typeof (string[])); + //subclassPropertyColumnNumberClosure = ArrayHelper.To2DIntArray(propColumnNumbers); + //subclassPropertyFormulaNumberClosure = ArrayHelper.To2DIntArray(propFormulaNumbers); subclassPropertyCascadeStyleClosure = new Cascades.CascadeStyle[cascades.Count]; int m = 0; @@ -1275,7 +1322,7 @@ return new EntityLoader(this, columns, uniqueKeyType, 1, LockMode.None, Factory, enabledFilters); } - public override IType Type + public IType Type { get { return entityMetamodel.EntityType; } } @@ -1373,7 +1420,7 @@ public virtual IType GetPropertyType(string path) { - return ToType(path); + return propertyMapping.ToType(path); } protected bool HasSelectBeforeUpdate @@ -1629,6 +1676,11 @@ return StringHelper.Root(GetType().FullName) + '(' + ClassName + ')'; } + public System.Type[] PropertySubclassNames + { + get { return propertySubclassNames; } + } + /// /// Get the column names for the numbered property of this class /// @@ -1860,7 +1912,12 @@ public abstract IType DiscriminatorType { get; } - public abstract SqlString FromJoinFragment(string alias, bool innerJoin, bool includeSubclasses); + public virtual SqlString FromJoinFragment(string alias, bool innerJoin, bool includeSubclasses) + { + return TableSpan == 1 + ? new SqlString(string.Empty) // just a performance opt! + : CreateJoin(alias, innerJoin, includeSubclasses).ToFromFragmentString; + } public abstract SqlString FromTableFragment(string alias); @@ -1871,8 +1928,14 @@ return propertyDefinedOnSubclass[i]; } + [Obsolete("Added parameter allProperties to follow H3's API")] public string PropertySelectFragment(string name, string suffix) { + return PropertySelectFragment(name, suffix, true); + } + + public string PropertySelectFragment(string name, string suffix, bool allProperties) + { SelectFragment select = new SelectFragment(Factory.Dialect) .SetSuffix(suffix) .SetUsedAliases(IdentifierAliases); @@ -1883,8 +1946,14 @@ for (int i = 0; i < columns.Length; i++) { - string subalias = Alias(name, columnTableNumbers[i]); - select.AddColumn(subalias, columns[i], columnAliases[i]); + bool selectable = (allProperties) && + !IsSubclassTableSequentialSelect(columnTableNumbers[i]) && + subclassColumnSelectableClosure[i]; + if (selectable) + { + string subalias = Alias(name, columnTableNumbers[i]); + select.AddColumn(subalias, columns[i], columnAliases[i]); + } } int[] formulaTableNumbers = SubclassFormulaTableNumberClosure; @@ -1893,8 +1962,13 @@ for (int i = 0; i < formulaTemplates.Length; i++) { - string subalias = Alias(name, formulaTableNumbers[i]); - select.AddFormula(subalias, formulaTemplates[i], formulaAliases[i]); + bool selectable = (allProperties) && + !IsSubclassTableSequentialSelect(formulaTableNumbers[i]); + if (selectable) + { + string subalias = Alias(name, formulaTableNumbers[i]); + select.AddFormula(subalias, formulaTemplates[i], formulaAliases[i]); + } } if (HasSubclasses) @@ -1938,6 +2012,11 @@ get { return subclassPropertyNameClosure; } } + protected System.Type[] SubclassPropertySubclassNameClosure + { + get { return this.subclassPropertySubclassNameClosure; } + } + protected IType[] SubclassPropertyTypeClosure { get { return subclassPropertyTypeClosure; } @@ -1990,6 +2069,114 @@ } /// + /// Unmarshall the fields of a persistent instance from a result set, + /// without resolving associations or collections + /// + /// + /// + /// + /// + /// + /// + /// + public object[] Hydrate( + IDataReader rs, + object id, + object obj, + ILoadable rootLoadable, + string[][] suffixedPropertyColumns, + ISessionImplementor session) + { + if (log.IsDebugEnabled) + { + log.Debug("Hydrating entity: " + rootLoadable.ClassName + '#' + id); + } + + AbstractEntityPersister rootPersister = (AbstractEntityPersister)rootLoadable; + + bool hasDeferred = rootPersister.HasSequentialSelect; + IDbCommand sequentialSelect = null; + IDataReader sequentialResultSet = null; + bool sequentialSelectEmpty = false; + try + { + if (hasDeferred) + { + SqlString sql = rootPersister.GetSequentialSelect(MappedClass); + if (sql != null) + { + //TODO: I am not so sure about the exception handling in this bit! + sequentialSelect = session.Batcher.PrepareCommand(CommandType.Text, sql, idSqlTypes); + rootPersister.IdentifierType.NullSafeSet(sequentialSelect, id, 0, session); + sequentialResultSet = session.Batcher.ExecuteReader(sequentialSelect); + if (!sequentialResultSet.Read()) + { + // TODO: Deal with the "optional" attribute in the mapping; + // this code assumes that optional defaults to "true" because it + // doesn't actually seem to work in the fetch="join" code + // + // Note that actual proper handling of optional-ality here is actually + // more involved than this patch assumes. Remember that we might have + // multiple mappings associated with a single entity. Really + // a couple of things need to happen to properly handle optional here: + // 1) First and foremost, when handling multiple s, we really + // should be using the entity root table as the driving table; + // another option here would be to choose some non-optional joined + // table to use as the driving table. In all likelihood, just using + // the root table is much simplier + // 2) Need to add the FK columns corresponding to each joined table + // to the generated select list; these would then be used when + // iterating the result set to determine whether all non-optional + // data is present + // My initial thoughts on the best way to deal with this would be + // to introduce a new SequentialSelect abstraction that actually gets + // generated in the persisters (ok, SingleTable...) and utilized here. + // It would encapsulated all this required optional-ality checking... + sequentialSelectEmpty = true; + } + } + } + + string[] propNames = PropertyNames; + IType[] types = PropertyTypes; + object[] values = new object[types.Length]; + // TODO: H3 - Property Laziness + // bool[] laziness = PropertyLaziness(); + System.Type[] propSubclassNames = SubclassPropertySubclassNameClosure; + + for (int i = 0; i < types.Length; i++) + { + bool propertyIsDeferred = hasDeferred + && rootPersister.IsSubclassPropertyDeferred(propNames[i], propSubclassNames[i]); + + IDataReader propertyResultSet = propertyIsDeferred ? sequentialResultSet : rs; + string[] cols = propertyIsDeferred ? propertyColumnAliases[i] : suffixedPropertyColumns[i]; + values[i] = types[i].Hydrate(propertyResultSet, cols, session, obj); + } + + if (sequentialResultSet != null) + { + sequentialResultSet.Close(); + } + + return values; + } + finally + { + if (sequentialSelect != null) + { + session.Batcher.CloseCommand(sequentialSelect, sequentialResultSet); + } + } + + } + + protected virtual SqlString GetSequentialSelect(System.Type entityName) + { + throw new NotSupportedException("no sequential selects"); + } + + /// /// Persist an object, using a natively generated identifier /// protected object Insert(object[] fields, bool[] notNull, SqlCommandInfo sql, object obj, ISessionImplementor session) @@ -2104,7 +2291,7 @@ public int GetSubclassPropertyTableNumber(string propertyPath) { string rootPropertyName = StringHelper.Root(propertyPath); - IType type = ToType(rootPropertyName); + IType type = propertyMapping.ToType(rootPropertyName); if (type.IsAssociationType && ((IAssociationType) type).UseLHSPrimaryKey) { return 0; @@ -2121,11 +2308,17 @@ return index == -1 ? 0 : GetSubclassPropertyTableNumber(index); } - public override string[] ToColumns(string alias, string propertyName) + // TODO: override + public virtual string[] ToColumns(string alias, string propertyName) { - return base.ToColumns(Alias(alias, GetSubclassPropertyTableNumber(propertyName)), propertyName); + return propertyMapping.ToColumns(alias, propertyName); } + public IType ToType(string propertyName) + { + return propertyMapping.ToType(propertyName); + } + public abstract SqlString WhereJoinFragment(string alias, bool innerJoin, bool includeSubclasses); public abstract string DiscriminatorColumnName { get; } @@ -2217,13 +2410,13 @@ // + StringHelper.Underscore ); } - protected virtual void AddDiscriminatorToSelect(SelectFragment select, string name, string suffix) - { - } + protected virtual void AddDiscriminatorToInsert(SqlInsertBuilder insert) { } + protected virtual void AddDiscriminatorToSelect(SelectFragment select, string name, string suffix) { } + public string[] GetPropertyColumnNames(string propertyName) { - return GetColumnNames(propertyName); + return propertyMapping.GetColumnNames(propertyName); } public int GetPropertyIndex(string propertyName) @@ -2238,6 +2431,134 @@ get { return entityMetamodel; } } + private JoinFragment CreateJoin(string name, bool innerjoin, bool includeSubclasses) + { + string[] idCols = StringHelper.Qualify(name, IdentifierColumnNames); //all joins join to the pk of the driving table + JoinFragment join = Factory.Dialect.CreateOuterJoinFragment(); + int tableSpan = SubclassTableSpan; + for (int j = 1; j < tableSpan; j++) //notice that we skip the first table; it is the driving table! + { + bool joinIsIncluded = IsClassOrSuperclassTable(j) || + (includeSubclasses && !IsSubclassTableSequentialSelect(j)); + if (joinIsIncluded) + { + join.AddJoin(GetSubclassTableName(j), + GenerateTableAlias(name, j), + idCols, + GetSubclassTableKeyColumns(j), + innerjoin && IsClassOrSuperclassTable(j) && !IsInverseTable(j) && !IsNullableTable(j) + ? JoinType.InnerJoin //we can inner join to superclass tables (the row MUST be there) + : JoinType.LeftOuterJoin //we can never inner join to subclass tables + ); + } + } + return join; + } + + private JoinFragment CreateJoin(int[] tableNumbers, string drivingAlias) + { + string[] keyCols = StringHelper.Qualify( drivingAlias, GetSubclassTableKeyColumns( tableNumbers[0] ) ); + JoinFragment jf = Factory.Dialect.CreateOuterJoinFragment(); + for ( int i = 1; i < tableNumbers.Length; i++ ) { //skip the driving table + int j = tableNumbers[i]; + jf.AddJoin( GetSubclassTableName( j ), + GenerateTableAlias( RootAlias, j ), + keyCols, + GetSubclassTableKeyColumns( j ), + IsInverseSubclassTable( j ) || IsNullableSubclassTable( j ) + ? JoinType.LeftOuterJoin + : JoinType.InnerJoin ); + } + return jf; + } + + protected SelectFragment CreateSelect(int[] subclassColumnNumbers, int[] subclassFormulaNumbers) + { + SelectFragment selectFragment = new SelectFragment(Factory.Dialect); + + int[] columnTableNumbers = SubclassColumnTableNumberClosure; + string[] columnAliases = SubclassColumnAliasClosure; + string[] columns = SubclassColumnClosure; + for (int i = 0; i < subclassColumnNumbers.Length; i++) + { + if (subclassColumnSelectableClosure[i]) + { + int columnNumber = subclassColumnNumbers[i]; + String subalias = GenerateTableAlias(RootAlias, columnTableNumbers[columnNumber]); + selectFragment.AddColumn(subalias, columns[columnNumber], columnAliases[columnNumber]); + } + } + + int[] formulaTableNumbers = SubclassFormulaTableNumberClosure; + String[] formulaTemplates = SubclassFormulaTemplateClosure; + String[] formulaAliases = SubclassFormulaAliasClosure; + for (int i = 0; i < subclassFormulaNumbers.Length; i++) + { + int formulaNumber = subclassFormulaNumbers[i]; + String subalias = GenerateTableAlias(RootAlias, formulaTableNumbers[formulaNumber]); + selectFragment.AddFormula(subalias, formulaTemplates[formulaNumber], formulaAliases[formulaNumber]); + } + + return selectFragment; + } + + protected string CreateFrom(int tableNumber, String alias) + { + return GetSubclassTableName(tableNumber) + ' ' + alias; + } + + protected SqlString CreateWhereByKey(int tableNumber, string alias) + { + //TODO: move to .sql package, and refactor with similar things! + //return new SqlString(StringHelper.Join("= ? and ", + // StringHelper.Qualify(alias, GetSubclassTableKeyColumns(tableNumber))) + "= ?"); + SqlStringBuilder builder = new SqlStringBuilder(); + string[] subclauses = StringHelper.Qualify(alias, GetSubclassTableKeyColumns(tableNumber)); + for (int i = 0; i < subclauses.Length; i++ ) + { + string subclause = subclauses[i]; + if (i > 0) + { + builder.Add(" and "); + } + builder.Add(subclause + "=").AddParameter(); + } + return builder.ToSqlString(); + } + + protected SqlString RenderSelect( + int[] tableNumbers, + int[] columnNumbers, + int[] formulaNumbers) + { + Array.Sort(tableNumbers); //get 'em in the right order (not that it really matters) + + //render the where and from parts + int drivingTable = tableNumbers[0]; + string drivingAlias = GenerateTableAlias( RootAlias, drivingTable ); //we *could* regerate this inside each called method! + SqlString where = CreateWhereByKey( drivingTable, drivingAlias ); + string from = CreateFrom( drivingTable, drivingAlias ); + + //now render the joins + JoinFragment jf = CreateJoin(tableNumbers, drivingAlias); + + //now render the select clause + SelectFragment selectFragment = CreateSelect(columnNumbers, formulaNumbers); + + //now tie it all together + SqlSelectBuilder select = new SqlSelectBuilder(Factory); + select.SetSelectClause(selectFragment.ToFragmentString().Substring(2)); + select.SetFromClause(from); + select.SetWhereClause(where); + select.SetOuterJoins(jf.ToFromFragmentString, jf.ToWhereFragmentString); + // TODO: H3 + //if (Factory.Settings.IsCommentsEnabled) + //{ + // select.SetComment("sequential select " + MappedClass.Name); + //} + return select.ToSqlString(); + } + private string RootAlias { get { return StringHelper.GenerateAlias(ClassName); } @@ -2299,25 +2620,25 @@ FromJoinFragment(RootAlias, true, false); SqlString joiner = new SqlString("=", Parameter.Placeholder, " and "); - SqlString whereClause = new SqlStringBuilder() + SqlStringBuilder whereClauseBuilder = new SqlStringBuilder() .Add(StringHelper.Join(joiner, aliasedIdColumns)) .Add("=") .AddParameter() - .Add(WhereJoinFragment(RootAlias, true, false)) - .ToSqlString(); + .Add(WhereJoinFragment(RootAlias, true, false)); // TODO H3: this is commented out in H3.2 if (IsVersioned) { - whereClause.Append(" and ") - .Append(VersionColumnName) - .Append("=?"); + whereClauseBuilder.Add(" and ") + .Add(VersionColumnName) + .Add("=") + .AddParameter(); } return select.SetSelectClause(selectClause) .SetFromClause(fromClause) .SetOuterJoins(SqlString.Empty, SqlString.Empty) - .SetWhereClause(whereClause) + .SetWhereClause(whereClauseBuilder.ToSqlString()) .ToSqlString(); } @@ -2390,7 +2711,7 @@ return sessionFilterFragment.Append(FilterFragment(alias)).ToString(); } - protected string GenerateTableAlias(string rootAlias, int tableNumber) + public string GenerateTableAlias(string rootAlias, int tableNumber) { if (tableNumber == 0) { @@ -2470,6 +2791,10 @@ return string.Empty; } + protected abstract bool IsClassOrSuperclassTable(int j); + + protected abstract int SubclassTableSpan { get; } + protected abstract int TableSpan { get; } protected SqlCommandInfo GenerateInsertString(bool[] includeProperty, int j) @@ -2487,8 +2812,45 @@ return GenerateInsertString(true, includeProperty); } - protected abstract SqlCommandInfo GenerateInsertString(bool identityInsert, bool[] notNull, int j); + protected virtual SqlCommandInfo GenerateInsertString(bool identityInsert, bool[] includeProperty, int j) + { + SqlInsertBuilder builder = new SqlInsertBuilder(Factory) + .SetTableName(GetTableName(j)); + // add normal properties + for (int i = 0; i < HydrateSpan; i++) + { + if (includeProperty[i] && IsPropertyOfTable(i, j)) + { + builder.AddColumns(GetPropertyColumnNames(i), PropertyColumnInsertable[i], PropertyTypes[i]); + } + } + + // add the discriminator + if (j == 0) + { + AddDiscriminatorToInsert(builder); + } + + // add the primary key + if (j == 0 && identityInsert) + { + // make sure the Dialect has an identity insert string because we don't want + // to add the column when there is no value to supply the SqlBuilder + if (Dialect.IdentityInsertString != null) + { + // only 1 column if there is IdentityInsert enabled. + builder.AddColumn(IdentifierColumnNames[0], Dialect.IdentityInsertString); + } + } + else + { + builder.AddColumns(GetKeyColumns(j), IdentifierType); + } + + return builder.ToSqlCommandInfo(); + } + public object Insert(object[] fields, object obj, ISessionImplementor session) { int span = TableSpan; @@ -2572,6 +2934,21 @@ get { return sqlUpdateStrings; } } + protected virtual bool IsSubclassPropertyDeferred(string propertyName, System.Type entityName) + { + return false; + } + + protected virtual bool IsSubclassTableSequentialSelect(int table) + { + return false; + } + + public virtual bool HasSequentialSelect + { + get { return false; } + } + protected abstract string GetTableName(int table); protected abstract string[] GetKeyColumns(int table); protected abstract bool IsPropertyOfTable(int property, int table); @@ -2943,5 +3320,35 @@ { get { return null; } } + + public virtual bool IsMultiTable + { + get { return false; } + } + + protected int PropertySpan + { + get { return EntityMetamodel.PropertySpan; } + } + + protected virtual bool IsInverseTable(int j) + { + return false; + } + + protected virtual bool IsNullableTable(int j) + { + return false; + } + + protected virtual bool IsNullableSubclassTable(int j) + { + return false; + } + + protected virtual bool IsInverseSubclassTable(int j) + { + return false; + } } } Index: src/NHibernate/Persister/Entity/AbstractPropertyMapping.cs =================================================================== --- src/NHibernate/Persister/Entity/AbstractPropertyMapping.cs (revision 2594) +++ src/NHibernate/Persister/Entity/AbstractPropertyMapping.cs (working copy) @@ -89,7 +89,7 @@ HandlePath( path, type ); } - protected void InitPropertyPaths( string path, IType type, string[] columns, string[] formulaTemplates, IMapping factory ) + protected internal void InitPropertyPaths( string path, IType type, string[] columns, string[] formulaTemplates, IMapping factory ) { if( columns.Length != type.GetColumnSpan( factory ) ) { Index: src/NHibernate/Persister/Entity/BasicEntityPropertyMapping.cs =================================================================== --- src/NHibernate/Persister/Entity/BasicEntityPropertyMapping.cs (revision 0) +++ src/NHibernate/Persister/Entity/BasicEntityPropertyMapping.cs (revision 0) @@ -0,0 +1,43 @@ +using System; +using NHibernate.Type; + +namespace NHibernate.Persister.Entity +{ + public class BasicEntityPropertyMapping : AbstractPropertyMapping + { + private readonly AbstractEntityPersister persister; + + public BasicEntityPropertyMapping(AbstractEntityPersister persister) + { + this.persister = persister; + } + + public override string[] IdentifierColumnNames + { + get { return persister.IdentifierColumnNames; } + } + + protected System.Type EntityName + { + get { return persister.MappedClass; } + } + + public override IType Type + { + get { return persister.Type; } + } + + public override string[] ToColumns(string alias, string propertyName) + { + return base.ToColumns( + persister.GenerateTableAlias(alias, persister.GetSubclassPropertyTableNumber(propertyName)), + propertyName + ); + } + + public override string ClassName + { + get { return Type.Name; } + } + } +} Index: src/NHibernate/Persister/Entity/ILoadable.cs =================================================================== --- src/NHibernate/Persister/Entity/ILoadable.cs (revision 2594) +++ src/NHibernate/Persister/Entity/ILoadable.cs (working copy) @@ -1,4 +1,6 @@ using NHibernate.Type; +using NHibernate.Engine; +using System.Data; namespace NHibernate.Persister.Entity { @@ -57,5 +59,25 @@ /// Does this entity own any collections which are fetchable by subselect? /// bool HasSubselectLoadableCollections { get; } + + /// + /// Retrieve property values from one row of a result set + /// + /// + /// + /// + /// + /// + /// + /// + object[] Hydrate( + IDataReader rs, + object id, + object obj, + ILoadable rootLoadable, + string[][] suffixedPropertyColumns, + ISessionImplementor session); + + bool IsAbstract { get; } } } \ No newline at end of file Index: src/NHibernate/Persister/Entity/IQueryable.cs =================================================================== --- src/NHibernate/Persister/Entity/IQueryable.cs (revision 2594) +++ src/NHibernate/Persister/Entity/IQueryable.cs (working copy) @@ -56,8 +56,11 @@ /// /// /// - string PropertySelectFragment( string alias, string suffix ); + string PropertySelectFragment(string alias, string suffix, bool allProperties); + // Obsolete. Use the above overload instead + string PropertySelectFragment(string alias, string suffix); + string GenerateFilterConditionAlias(string rootAlias); } } Index: src/NHibernate/Persister/Entity/JoinedSubclassEntityPersister.cs =================================================================== --- src/NHibernate/Persister/Entity/JoinedSubclassEntityPersister.cs (revision 2594) +++ src/NHibernate/Persister/Entity/JoinedSubclassEntityPersister.cs (working copy) @@ -595,7 +595,8 @@ return 0; } - protected override void HandlePath(string path, IType type) + // TODO: override + protected void HandlePath(string path, IType type) { if (type.IsAssociationType && ((IAssociationType) type).UseLHSPrimaryKey) { @@ -660,6 +661,7 @@ throw new AssertionFailure(string.Format("table [{0}] not found", tableName)); } + // TODO: override public override string[] ToColumns(string alias, string property) { if (PathExpressionParser.EntityClass.Equals(property)) @@ -786,6 +788,31 @@ get { return tableSpan; } } + public override bool IsMultiTable + { + get { return true; } + } + + protected override string[] GetSubclassTableKeyColumns(int j) + { + return subclassTableKeyColumns[j]; + } + + public override string GetSubclassTableName(int j) + { + return subclassTableNameClosure[j]; + } + + protected override int SubclassTableSpan + { + get { return subclassTableNameClosure.Length; } + } + + protected override bool IsClassOrSuperclassTable(int j) + { + return isClassOrSuperclassTable[j]; + } + protected override string[] GetKeyColumns(int table) { return naturalOrderTableKeyColumns[table]; Index: src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs =================================================================== --- src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs (revision 2594) +++ src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs (working copy) @@ -7,9 +7,12 @@ using NHibernate.Mapping; using NHibernate.SqlCommand; using NHibernate.Type; +using NHibernate.Util; namespace NHibernate.Persister.Entity { + using NHibernate.Mapping; + /// /// Default implementation of the ClassPersister interface. Implements the /// "table-per-class hierarchy" mapping strategy for an entity class. @@ -17,10 +20,36 @@ public class SingleTableEntityPersister : AbstractEntityPersister, IQueryable { // the class hierarchy structure - private readonly string qualifiedTableName; - private readonly string[] tableNames; + private readonly int joinSpan; + private readonly string[] qualifiedTableNames; + private readonly bool[] isInverseTable; + private readonly bool[] isNullableTable; + private readonly string[][] keyColumnNames; + private readonly bool[] cascadeDeleteEnabled; + private readonly bool hasSequentialSelects; + + private readonly String[] spaces; + private readonly System.Type[] subclassClosure; + private readonly string[] subclassTableNameClosure; + private readonly bool[] subclassTableIsLazyClosure; + private readonly bool[] isInverseSubclassTable; + private readonly bool[] isNullableSubclassTable; + private readonly bool[] subclassTableSequentialSelect; + private readonly string[][] subclassTableKeyColumnClosure; + private readonly bool[] isClassOrSuperclassTable; + + // properties of this class, including inherited properties + private readonly int[] propertyTableNumbers; + + // the closure of all columns used by the entire hierarchy including + // subclasses and superclasses of this class + private readonly int[] subclassPropertyTableNumberClosure; + + private readonly int[] subclassColumnTableNumberClosure; + private readonly int[] subclassFormulaTableNumberClosure; + // discriminator column private readonly Hashtable subclassesByDiscriminatorValue = new Hashtable(); private readonly bool forceDiscriminator; @@ -33,8 +62,15 @@ private readonly object discriminatorValue; private readonly bool discriminatorInsertable; - private readonly int[] propertyTableNumbers; + private readonly string[] constraintOrderedTableNames; + private readonly string[][] constraintOrderedKeyColumnNames; + //private readonly IDictionary propertyTableNumbersByName = new Hashtable(); + private readonly IDictionary propertyTableNumbersByNameAndSubclass = new Hashtable(); + + private readonly IDictionary sequentialSelectStringsByEntityName = new Hashtable(); + + private static readonly object NullDiscriminator = new object(); private static readonly object NotNullDiscriminator = new object(); @@ -55,7 +91,7 @@ public override string TableName { - get { return qualifiedTableName; } + get { return qualifiedTableNames[0]; } } public override IType DiscriminatorType @@ -97,7 +133,7 @@ public override object[] PropertySpaces { - get { return tableNames; } + get { return qualifiedTableNames; } } protected bool IsDiscriminatorFormula @@ -110,42 +146,6 @@ get { return discriminatorFormula; } } - protected override SqlCommandInfo GenerateInsertString(bool identityInsert, bool[] includeProperty, int j) - { - SqlInsertBuilder builder = new SqlInsertBuilder(Factory) - .SetTableName(TableName); - - for (int i = 0; i < HydrateSpan; i++) - { - if (includeProperty[i]) - { - builder.AddColumns( GetPropertyColumnNames( i ), PropertyColumnInsertable[i], PropertyTypes[i] ); - } - } - - if (discriminatorInsertable) - { - builder.AddColumn(DiscriminatorColumnName, DiscriminatorSQLValue); - } - - if (!identityInsert) - { - builder.AddColumn(IdentifierColumnNames, IdentifierType); - } - else - { - // make sure the Dialect has an identity insert string because we don't want - // to add the column when there is no value to supply the SqlBuilder - if (Dialect.IdentityInsertString != null) - { - // only 1 column if there is IdentityInsert enabled. - builder.AddColumn(IdentifierColumnNames[0], Dialect.IdentityInsertString); - } - } - - return builder.ToSqlCommandInfo(); - } - /// /// Generate the SQL that selects a row by id using FOR UPDATE /// @@ -265,23 +265,35 @@ public SingleTableEntityPersister(PersistentClass model, ICacheConcurrencyStrategy cache, ISessionFactoryImplementor factory, IMapping mapping) : base(model, cache, factory) { + int i; + // CLASS + TABLE + #region CLASS + TABLE System.Type mappedClass = model.MappedClass; + + joinSpan = model.JoinClosureSpan + 1; + qualifiedTableNames = new string[joinSpan]; + isInverseTable = new bool[joinSpan]; + isNullableTable = new bool[joinSpan]; + keyColumnNames = new string[joinSpan][]; Table table = model.RootTable; - qualifiedTableName = table.GetQualifiedName(Dialect, factory.DefaultSchema); - tableNames = new string[] {qualifiedTableName}; + qualifiedTableNames[0] = table.GetQualifiedName(Dialect, factory.DefaultSchema); + isInverseTable[0] = false; + isNullableTable[0] = false; + keyColumnNames[0] = this.IdentifierColumnNames; + cascadeDeleteEnabled = new bool[joinSpan]; // Custom sql - customSQLInsert = new SqlString[1]; - customSQLUpdate = new SqlString[1]; - customSQLDelete = new SqlString[1]; - insertCallable = new bool[1]; - updateCallable = new bool[1]; - deleteCallable = new bool[1]; - insertResultCheckStyles = new ExecuteUpdateResultCheckStyle[1]; - updateResultCheckStyles = new ExecuteUpdateResultCheckStyle[1]; - deleteResultCheckStyles = new ExecuteUpdateResultCheckStyle[1]; + customSQLInsert = new SqlString[joinSpan]; + customSQLUpdate = new SqlString[joinSpan]; + customSQLDelete = new SqlString[joinSpan]; + insertCallable = new bool[joinSpan]; + updateCallable = new bool[joinSpan]; + deleteCallable = new bool[joinSpan]; + insertResultCheckStyles = new ExecuteUpdateResultCheckStyle[joinSpan]; + updateResultCheckStyles = new ExecuteUpdateResultCheckStyle[joinSpan]; + deleteResultCheckStyles = new ExecuteUpdateResultCheckStyle[joinSpan]; customSQLInsert[0] = model.CustomSQLInsert; insertCallable[0] = customSQLInsert[0] != null && model.IsCustomInsertCallable; @@ -301,11 +313,117 @@ ? ExecuteUpdateResultCheckStyle.DetermineDefault(customSQLDelete[0], deleteCallable[0]) : model.CustomSQLDeleteCheckStyle; + #endregion + // JOINS + #region JOINS + + int j = 1; + foreach (Join join in model.JoinClosureCollection) + { + qualifiedTableNames[j] = join.Table.GetQualifiedName( + factory.Dialect, + factory.Settings.DefaultSchemaName + ); + isInverseTable[j] = join.IsInverse; + isNullableTable[j] = join.IsOptional; + //cascadeDeleteEnabled[j] = join.Key.IsCascadeDeleteEnabled && factory.Dialect.SupportsCascadeDelete; + + customSQLInsert[j] = join.CustomSQLInsert; + insertCallable[j] = customSQLInsert[j] != null && join.IsCustomInsertCallable; + insertResultCheckStyles[j] = join.CustomSQLInsertCheckStyle == null + ? ExecuteUpdateResultCheckStyle.DetermineDefault(customSQLInsert[j], insertCallable[j]) + : join.CustomSQLInsertCheckStyle; + customSQLUpdate[j] = join.CustomSQLUpdate; + updateCallable[j] = customSQLUpdate[j] != null && join.IsCustomUpdateCallable; + updateResultCheckStyles[j] = join.CustomSQLUpdateCheckStyle == null + ? ExecuteUpdateResultCheckStyle.DetermineDefault(customSQLUpdate[j], updateCallable[j]) + : join.CustomSQLUpdateCheckStyle; + customSQLDelete[j] = join.CustomSQLDelete; + deleteCallable[j] = customSQLDelete[j] != null && join.IsCustomDeleteCallable; + deleteResultCheckStyles[j] = join.CustomSQLDeleteCheckStyle == null + ? ExecuteUpdateResultCheckStyle.DetermineDefault(customSQLDelete[j], deleteCallable[j]) + : join.CustomSQLDeleteCheckStyle; + + ICollection keyColumns = join.Key.ColumnCollection; + keyColumnNames[j] = new string[join.Key.ColumnSpan]; + i = 0; + foreach (Column col in keyColumns) + { + keyColumnNames[j][i++] = col.GetQuotedName(factory.Dialect); + } + + j++; + } + + constraintOrderedTableNames = new string[qualifiedTableNames.Length]; + constraintOrderedKeyColumnNames = new string[qualifiedTableNames.Length][]; + for (int k = qualifiedTableNames.Length - 1, position = 0; k >= 0; k--, position++) + { + constraintOrderedTableNames[position] = qualifiedTableNames[k]; + constraintOrderedKeyColumnNames[position] = keyColumnNames[k]; + } + + spaces = ArrayHelper.Join( + qualifiedTableNames, + ArrayHelper.ToStringArray(model.SynchronizedTables)); + + // TODO: H3 - IsInstrumented depends on EntityModel + //bool lazyAvailable = IsInstrumented(); + + bool hasDeferred = false; + ArrayList subclassTables = new ArrayList(); + ArrayList joinKeyColumns = new ArrayList(); + ArrayList isConcretes = new ArrayList(); + ArrayList isDeferreds = new ArrayList(); + ArrayList isInverses = new ArrayList(); + ArrayList isNullables = new ArrayList(); + ArrayList isLazies = new ArrayList(); + subclassTables.Add(qualifiedTableNames[0]); + joinKeyColumns.Add(IdentifierColumnNames); + isConcretes.Add(false); + isDeferreds.Add(false); + isInverses.Add(false); + isNullables.Add(false); + isLazies.Add(false); + + foreach (Join join in model.SubclassJoinClosureCollection) + { + + isConcretes.Add(model.IsClassOrSuperclassJoin(join)); + isDeferreds.Add(join.IsSequentialSelect); + isInverses.Add(join.IsInverse); + isNullables.Add(join.IsOptional); + // TODO: Fix isLazies when lazy column is implemented + isLazies.Add(false); //isLazies.Add(lazyAvailable && join.isLazy); + if (join.IsSequentialSelect && !model.IsClassOrSuperclassJoin(join)) + hasDeferred = true; + subclassTables.Add(join.Table.GetQualifiedName(factory.Dialect, factory.DefaultSchema)); + string[] keyCols = new string[join.Key.ColumnSpan]; + int k = 0; + foreach (Column col in join.Key.ColumnCollection) + { + keyCols[k++] = col.GetQuotedName(factory.Dialect); + } + joinKeyColumns.AddRange(keyCols); + } + + subclassTableSequentialSelect = ArrayHelper.ToBooleanArray(isDeferreds); + subclassTableNameClosure = ArrayHelper.ToStringArray(subclassTables); + subclassTableIsLazyClosure = ArrayHelper.ToBooleanArray(isLazies); + subclassTableKeyColumnClosure = ArrayHelper.To2DStringArray(joinKeyColumns); + isClassOrSuperclassTable = ArrayHelper.ToBooleanArray(isConcretes); + isInverseSubclassTable = ArrayHelper.ToBooleanArray(isInverses); + isNullableSubclassTable = ArrayHelper.ToBooleanArray(isNullables); + hasSequentialSelects = hasDeferred; + + #endregion + // detect mapping errors HashedSet distinctColumns = new HashedSet(); // DISCRIMINATOR + #region DISCRIMINATOR if (model.IsPolymorphic) { IValue d = model.Discriminator; @@ -387,15 +505,43 @@ discriminatorValue = null; discriminatorSQLValue = null; } + #endregion // PROPERTIES - HashedSet thisClassProperties = new HashedSet(); + #region PROPERTIES - foreach (Mapping.Property prop in model.PropertyClosureCollection) + propertyTableNumbers = new int[PropertySpan]; + i = 0; + foreach (Property prop in model.PropertyClosureCollection) { - thisClassProperties.Add(prop); + propertyTableNumbers[i++] = model.GetJoinNumber(prop); } + ArrayList columnJoinNumbers = new ArrayList(); + ArrayList formulaJoinedNumbers = new ArrayList(); + ArrayList propertyJoinNumbers = new ArrayList(); + + foreach (Property prop in model.SubclassPropertyClosureCollection) + { + int joinNumber = model.GetJoinNumber(prop); + propertyJoinNumbers.Add(joinNumber); + + propertyTableNumbersByNameAndSubclass.Add( + prop.PersistentClass.MappedClass.FullName + "." + prop.Name, + joinNumber); + + foreach (ISelectable thing in prop.ColumnCollection) + { + if (thing.IsFormula) + formulaJoinedNumbers.Add(joinNumber); + else + columnJoinNumbers.Add(joinNumber); + } + } + subclassColumnTableNumberClosure = ArrayHelper.ToIntArray(columnJoinNumbers); + subclassFormulaTableNumberClosure = ArrayHelper.ToIntArray(formulaJoinedNumbers); + subclassPropertyTableNumberClosure = ArrayHelper.ToIntArray(propertyJoinNumbers); + // SQL string generation moved to PostInstantiate int subclassSpan = model.SubclassSpan + 1; @@ -405,8 +551,10 @@ { subclassesByDiscriminatorValue.Add(discriminatorValue, mappedClass); } + #endregion // SUBCLASSES + #region SUBCLASSES if (model.IsPolymorphic) { int k = 1; @@ -441,20 +589,26 @@ } } } + #endregion // This is in PostInstatiate as it needs identifier info //InitLockers(); - propertyTableNumbers = new int[EntityMetamodel.PropertySpan]; - for (int i = 0; i < propertyTableNumbers.Length; i++) - { - propertyTableNumbers[i] = 0; - } + InitSubclassPropertyAliasesMap(model); - InitSubclassPropertyAliasesMap(model); PostConstruct(mapping); } + protected override bool IsInverseTable(int j) + { + return isInverseTable[j]; + } + + protected override bool IsInverseSubclassTable(int j) + { + return isInverseSubclassTable[j]; + } + public override SqlString FromTableFragment(string alias) { return new SqlString(TableName + ' ' + alias); @@ -513,14 +667,9 @@ public override string GetSubclassPropertyTableName(int i) { - return qualifiedTableName; + return subclassTableNameClosure[subclassPropertyTableNumberClosure[i]]; } - public override SqlString FromJoinFragment(string alias, bool innerJoin, bool includeSubclasses) - { - return SqlString.Empty; - } - public override SqlString WhereJoinFragment(string alias, bool innerJoin, bool includeSubclasses) { return SqlString.Empty; @@ -535,14 +684,111 @@ /// protected override string VersionedTableName { - get { return qualifiedTableName; } + get { return qualifiedTableNames[0]; } } - protected override int GetSubclassPropertyTableNumber(int i) + protected override bool IsSubclassPropertyDeferred(string propertyName, System.Type entityName) { - return 0; + return hasSequentialSelects && + IsSubclassTableSequentialSelect(GetSubclassPropertyTableNumber(propertyName, entityName)); } + public override bool HasSequentialSelect + { + get { return hasSequentialSelects; } + } + + public int GetSubclassPropertyTableNumber(string propertyPath, System.Type entityName) + { + IType type = propertyMapping.ToType(propertyPath); + if (type.IsAssociationType && ((IAssociationType)type).UseLHSPrimaryKey) + return 0; + string propertyFullName = entityName.FullName + '.' + propertyPath; + if (propertyTableNumbersByNameAndSubclass.Contains(propertyFullName)) + { + return (int)propertyTableNumbersByNameAndSubclass[propertyFullName]; + } + else + { + return 0; + } + } + + protected override SqlString GetSequentialSelect(System.Type entityName) + { + return (SqlString)sequentialSelectStringsByEntityName[entityName]; + } + + private SqlString GenerateSequentialSelect(ILoadable persister) + { + //note that this method could easily be moved up to BasicEntityPersister, + //if we ever needed to reuse it from other subclasses + + //figure out which tables need to be fetched + AbstractEntityPersister subclassPersister = (AbstractEntityPersister)persister; + HashedSet tableNumbers = new HashedSet(); + string[] props = subclassPersister.PropertyNames; + System.Type[] classes = subclassPersister.PropertySubclassNames; + for (int i = 0; i < props.Length; i++) + { + int propTableNumber = GetSubclassPropertyTableNumber(props[i], classes[i]); + if (IsSubclassTableSequentialSelect(propTableNumber) && !IsSubclassTableLazy(propTableNumber)) + { + tableNumbers.Add(propTableNumber); + } + } + if (tableNumbers.IsEmpty) return null; + + //figure out which columns are needed + ArrayList columnNumbers = new ArrayList(); + int[] columnTableNumbers = SubclassColumnTableNumberClosure; + for (int i = 0; i < SubclassColumnClosure.Length; i++) + { + if (tableNumbers.Contains(columnTableNumbers[i])) + { + columnNumbers.Add(i); + } + } + + //figure out which formulas are needed + ArrayList formulaNumbers = new ArrayList(); + int[] formulaTableNumbers = SubclassColumnTableNumberClosure; + for (int i = 0; i < SubclassFormulaTemplateClosure.Length; i++) + { + if (tableNumbers.Contains(formulaTableNumbers[i])) + { + formulaNumbers.Add(i); + } + } + + //render the SQL + return RenderSelect( + ArrayHelper.ToIntArray(tableNumbers), + ArrayHelper.ToIntArray(columnNumbers), + ArrayHelper.ToIntArray(formulaNumbers) + ); + } + + protected override string[] GetSubclassTableKeyColumns(int j) + { + return subclassTableKeyColumnClosure[j]; + } + + public override string GetSubclassTableName(int j) + { + return subclassTableNameClosure[j]; + } + + protected override int SubclassTableSpan + { + get { return subclassTableNameClosure.Length; } + } + + protected override bool IsClassOrSuperclassTable(int j) + { + return isClassOrSuperclassTable[j]; + } + protected override void AddDiscriminatorToSelect(SelectFragment select, string name, string suffix) { if (IsDiscriminatorFormula) @@ -555,14 +801,27 @@ } } + protected override int GetSubclassPropertyTableNumber(int i) + { + return subclassPropertyTableNumberClosure[i]; + } + + protected override void AddDiscriminatorToInsert(SqlInsertBuilder insert) + { + if (discriminatorInsertable) + { + insert.AddColumn(DiscriminatorColumnName, DiscriminatorSQLValue); + } + } + protected override int[] SubclassColumnTableNumberClosure { - get { return new int[SubclassColumnClosure.Length]; } + get { return this.subclassColumnTableNumberClosure; } } protected override int[] SubclassFormulaTableNumberClosure { - get { return new int[SubclassFormulaClosure.Length]; } + get { return this.subclassFormulaTableNumberClosure; } } public override string GetPropertyTableName(string propertyName) @@ -635,27 +894,72 @@ protected override int TableSpan { - get { return 1; } + get { return joinSpan; } } protected override bool IsPropertyOfTable(int property, int table) { - return true; + return propertyTableNumbers[property] == table; } + protected override bool IsSubclassTableSequentialSelect(int table) + { + return subclassTableSequentialSelect[table] && !isClassOrSuperclassTable[table]; + } + protected override string[] GetKeyColumns(int table) { - return KeyColumnNames; + return keyColumnNames[table]; } protected override string GetTableName(int table) { - return tableNames[table]; + return qualifiedTableNames[table]; } protected override int[] PropertyTableNumbers { get { return propertyTableNumbers; } } + + protected bool IsSubclassTableLazy(int j) + { + return subclassTableIsLazyClosure[j]; + } + + protected override bool IsNullableTable(int j) + { + return isNullableTable[j]; + } + + protected override bool IsNullableSubclassTable(int j) + { + return isNullableSubclassTable[j]; + } + + public override void PostInstantiate() + { + base.PostInstantiate(); + if (hasSequentialSelects) + { + System.Type[] entityNames = SubclassClosure; + for (int i = 1; i < entityNames.Length; i++) + { + ILoadable loadable = (ILoadable)Factory.GetEntityPersister(entityNames[i]); + if (!loadable.IsAbstract) + { //perhaps not really necessary... + SqlString sequentialSelect = GenerateSequentialSelect(loadable); + sequentialSelectStringsByEntityName[entityNames[i]] = sequentialSelect; + } + } + } + } + + public override bool IsMultiTable + { + get { return TableSpan > 1; } + } + + } } Index: src/NHibernate/SqlCommand/SelectFragment.cs =================================================================== --- src/NHibernate/SqlCommand/SelectFragment.cs (revision 2594) +++ src/NHibernate/SqlCommand/SelectFragment.cs (working copy) @@ -108,6 +108,19 @@ return this; } + /// + /// Equivalent to ToSqlStringFragment. + /// + /// + /// + /// In H3, it is called ToFragmentString(). It appears to be + /// functionally equivalent as ToSqlStringFragment() here. + /// + public string ToFragmentString() + { + return ToSqlStringFragment(); + } + public string ToSqlStringFragment() { // this preserves the way this existing method works now. Index: src/NHibernate/SqlCommand/SqlInsertBuilder.cs =================================================================== --- src/NHibernate/SqlCommand/SqlInsertBuilder.cs (revision 2594) +++ src/NHibernate/SqlCommand/SqlInsertBuilder.cs (working copy) @@ -44,13 +44,19 @@ return this; } + [Obsolete("Renamed this overload to AddColumns (with an \"s\")")] + public SqlInsertBuilder AddColumn(string[] columnNames, IType propertyType) + { + return AddColumns(columnNames, propertyType); + } + /// /// Adds the Property's columns to the INSERT sql /// /// An array of the column names for the Property /// The IType of the property. /// The SqlInsertBuilder. - public SqlInsertBuilder AddColumn( string[ ] columnNames, IType propertyType ) + public SqlInsertBuilder AddColumns( string[ ] columnNames, IType propertyType ) { AddColumns( columnNames, null, propertyType ); return this; Index: src/NHibernate/SqlCommand/SqlSelectBuilder.cs =================================================================== --- src/NHibernate/SqlCommand/SqlSelectBuilder.cs (revision 2594) +++ src/NHibernate/SqlCommand/SqlSelectBuilder.cs (working copy) @@ -152,6 +152,15 @@ #region ISqlStringBuilder Members + /// + /// ToSqlString() is named ToStatementString() in H3 + /// + /// + public SqlString ToStatementString() + { + return ToSqlString(); + } + /// public SqlString ToSqlString() { Index: src/NHibernate/Util/ArrayHelper.cs =================================================================== --- src/NHibernate/Util/ArrayHelper.cs (revision 2594) +++ src/NHibernate/Util/ArrayHelper.cs (working copy) @@ -76,6 +76,11 @@ return (int[]) ToArray(coll, typeof (int)); } + public static bool[] ToBooleanArray(ICollection col) + { + return (bool[])ToArray(col, typeof(bool)); + } + public static string[] Slice( string[] strings, int begin, int length ) { string[] result = new string[ length ]; @@ -130,7 +135,26 @@ public static string[][] To2DStringArray( ICollection coll ) { string[][] result = new string[ coll.Count ][]; - coll.CopyTo( result, 0 ); + int i = 0; + foreach (object row in coll) + { + if (row is ICollection) + { + result[i] = new string[((ICollection)row).Count]; + int j = 0; + foreach (object cell in (ICollection)row) + { + result[i][j++] = cell == null ? null : (string)cell; + } + } + else + { + result[i] = new string[1]; + result[i][0] = row == null ? null : (string)row; + } + i++; + } + return result; }