diff --git a/src/NHibernate.Test/Async/Criteria/CriteriaQueryTest.cs b/src/NHibernate.Test/Async/Criteria/CriteriaQueryTest.cs index 7e3112b3f94..b02d678a811 100644 --- a/src/NHibernate.Test/Async/Criteria/CriteriaQueryTest.cs +++ b/src/NHibernate.Test/Async/Criteria/CriteriaQueryTest.cs @@ -13,6 +13,7 @@ using System.Collections.Generic; using NHibernate.Dialect; using NHibernate.Criterion; +using NHibernate.Linq; using NHibernate.SqlCommand; using NHibernate.Transform; using NHibernate.Type; @@ -922,6 +923,176 @@ public async Task ProjectionsTestAsync() s.Close(); } + [Test] + public async Task TestSQLProjectionWithAliasesAsync() + { + using(ISession s = OpenSession()) + using(ITransaction t = s.BeginTransaction()) + { + Course course = new Course(); + course.CourseCode = "HIB"; + course.Description = "Hibernate Training"; + await (s.SaveAsync(course)); + + Student gavin = new Student(); + gavin.Name = "Gavin King"; + gavin.StudentNumber = 667; + await (s.SaveAsync(gavin)); + + Student xam = new Student(); + xam.Name = "Max Rydahl Andersen"; + xam.StudentNumber = 101; + await (s.SaveAsync(xam)); + + Enrolment enrolment = new Enrolment(); + enrolment.Course = course; + enrolment.CourseCode = course.CourseCode; + enrolment.Semester = 1; + enrolment.Year = 1999; + enrolment.Student = xam; + enrolment.StudentNumber = xam.StudentNumber; + xam.Enrolments.Add(enrolment); + await (s.SaveAsync(enrolment)); + + enrolment = new Enrolment(); + enrolment.Course = course; + enrolment.CourseCode = course.CourseCode; + enrolment.Semester = 3; + enrolment.Year = 1998; + enrolment.Student = gavin; + enrolment.StudentNumber = gavin.StudentNumber; + gavin.Enrolments.Add(enrolment); + await (s.SaveAsync(enrolment)); + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + { + Student studentSubquery = null; + var subquery = QueryOver.Of(() => studentSubquery) + .And( + Expression.Sql("{e}.studentId = 667 and {studentSubquery}.studentId = 667")).Select(Projections.Id()); + + var uniqueResult = await (s.CreateCriteria(typeof(Student)) + .Add(Subqueries.Exists(subquery.DetachedCriteria)) + .AddOrder(Order.Asc("Name")) + .CreateCriteria("Enrolments", "e") + .AddOrder(Order.Desc("Year")) + .AddOrder(Order.Desc("Semester")) + .CreateCriteria("Course", "c") + .AddOrder(Order.Asc("Description")) + .SetProjection( + Projections.SqlProjection( + "{alias}.studentId as studentNumber, {e}.Semester as semester," + + " {c}.CourseCode as courseCode, {c}.Description as descr", + new string[] {"studentNumber", "semester", "courseCode", "descr"}, + new[] + { + TypeFactory.HeuristicType(typeof(long)), + TypeFactory.HeuristicType(typeof(short)), + TypeFactory.HeuristicType(typeof(string)), + TypeFactory.HeuristicType(typeof(string)), + })) + .UniqueResultAsync()); + + Assert.That(uniqueResult, Is.Not.Null); + } + + using (var s = OpenSession()) + using (s.BeginTransaction()) + { + await (s.Query().DeleteAsync()); + await (s.Query().DeleteAsync()); + await (s.Query().DeleteAsync()); + await (s.GetCurrentTransaction().CommitAsync()); + } + } + + [Test] + public async Task TestSQLProjectionWithDuplicateAliasesAsync() + { + using(ISession s = OpenSession()) + using(ITransaction t = s.BeginTransaction()) + { + Course course = new Course(); + course.CourseCode = "HIB"; + course.Description = "Hibernate Training"; + await (s.SaveAsync(course)); + + Student gavin = new Student(); + gavin.Name = "Gavin King"; + gavin.StudentNumber = 667; + await (s.SaveAsync(gavin)); + + Student xam = new Student(); + xam.Name = "Max Rydahl Andersen"; + xam.StudentNumber = 101; + await (s.SaveAsync(xam)); + + Enrolment enrolment = new Enrolment(); + enrolment.Course = course; + enrolment.CourseCode = course.CourseCode; + enrolment.Semester = 1; + enrolment.Year = 1999; + enrolment.Student = xam; + enrolment.StudentNumber = xam.StudentNumber; + xam.Enrolments.Add(enrolment); + await (s.SaveAsync(enrolment)); + + enrolment = new Enrolment(); + enrolment.Course = course; + enrolment.CourseCode = course.CourseCode; + enrolment.Semester = 3; + enrolment.Year = 1998; + enrolment.Student = gavin; + enrolment.StudentNumber = gavin.StudentNumber; + gavin.Enrolments.Add(enrolment); + await (s.SaveAsync(enrolment)); + await (t.CommitAsync()); + } + + using (var s = OpenSession()) + { + Student student = null; + var subquery = QueryOver.Of(() => student) + .And( + Expression.Sql("{e}.studentId = 667 and {student}.studentId = 667")).Select(Projections.Id()); + + var uniqueResult = await (s.CreateCriteria(typeof(Student), "student") + .Add(Subqueries.Exists(subquery.DetachedCriteria)) + .AddOrder(Order.Asc("Name")) + .CreateCriteria("Enrolments", "e") + .AddOrder(Order.Desc("Year")) + .AddOrder(Order.Desc("Semester")) + .CreateCriteria("Course", "c") + .AddOrder(Order.Asc("Description")) + .SetProjection( + Projections.SqlProjection( + "{alias}.studentId as studentNumber, {e}.Semester as semester," + + " {c}.CourseCode as courseCode, {c}.Description as descr", + new string[] {"studentNumber", "semester", "courseCode", "descr"}, + new[] + { + TypeFactory.HeuristicType(typeof(long)), + TypeFactory.HeuristicType(typeof(short)), + TypeFactory.HeuristicType(typeof(string)), + TypeFactory.HeuristicType(typeof(string)), + })) + .UniqueResultAsync()); + + Assert.That(uniqueResult, Is.Not.Null); + } + + using (var s = OpenSession()) + using (s.BeginTransaction()) + { + await (s.Query().DeleteAsync()); + await (s.Query().DeleteAsync()); + await (s.Query().DeleteAsync()); + await (s.GetCurrentTransaction().CommitAsync()); + } + } + [Test] public async Task CloningProjectionsTestAsync() { diff --git a/src/NHibernate.Test/Criteria/CriteriaQueryTest.cs b/src/NHibernate.Test/Criteria/CriteriaQueryTest.cs index b173b8c6987..a072957cb3f 100644 --- a/src/NHibernate.Test/Criteria/CriteriaQueryTest.cs +++ b/src/NHibernate.Test/Criteria/CriteriaQueryTest.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using NHibernate.Dialect; using NHibernate.Criterion; +using NHibernate.Linq; using NHibernate.SqlCommand; using NHibernate.Transform; using NHibernate.Type; @@ -1019,6 +1020,176 @@ public void ProjectionsTest() s.Close(); } + [Test] + public void TestSQLProjectionWithAliases() + { + using(ISession s = OpenSession()) + using(ITransaction t = s.BeginTransaction()) + { + Course course = new Course(); + course.CourseCode = "HIB"; + course.Description = "Hibernate Training"; + s.Save(course); + + Student gavin = new Student(); + gavin.Name = "Gavin King"; + gavin.StudentNumber = 667; + s.Save(gavin); + + Student xam = new Student(); + xam.Name = "Max Rydahl Andersen"; + xam.StudentNumber = 101; + s.Save(xam); + + Enrolment enrolment = new Enrolment(); + enrolment.Course = course; + enrolment.CourseCode = course.CourseCode; + enrolment.Semester = 1; + enrolment.Year = 1999; + enrolment.Student = xam; + enrolment.StudentNumber = xam.StudentNumber; + xam.Enrolments.Add(enrolment); + s.Save(enrolment); + + enrolment = new Enrolment(); + enrolment.Course = course; + enrolment.CourseCode = course.CourseCode; + enrolment.Semester = 3; + enrolment.Year = 1998; + enrolment.Student = gavin; + enrolment.StudentNumber = gavin.StudentNumber; + gavin.Enrolments.Add(enrolment); + s.Save(enrolment); + t.Commit(); + } + + using (var s = OpenSession()) + { + Student studentSubquery = null; + var subquery = QueryOver.Of(() => studentSubquery) + .And( + Expression.Sql("{e}.studentId = 667 and {studentSubquery}.studentId = 667")).Select(Projections.Id()); + + var uniqueResult = s.CreateCriteria(typeof(Student)) + .Add(Subqueries.Exists(subquery.DetachedCriteria)) + .AddOrder(Order.Asc("Name")) + .CreateCriteria("Enrolments", "e") + .AddOrder(Order.Desc("Year")) + .AddOrder(Order.Desc("Semester")) + .CreateCriteria("Course", "c") + .AddOrder(Order.Asc("Description")) + .SetProjection( + Projections.SqlProjection( + "{alias}.studentId as studentNumber, {e}.Semester as semester," + + " {c}.CourseCode as courseCode, {c}.Description as descr", + new string[] {"studentNumber", "semester", "courseCode", "descr"}, + new[] + { + TypeFactory.HeuristicType(typeof(long)), + TypeFactory.HeuristicType(typeof(short)), + TypeFactory.HeuristicType(typeof(string)), + TypeFactory.HeuristicType(typeof(string)), + })) + .UniqueResult(); + + Assert.That(uniqueResult, Is.Not.Null); + } + + using (var s = OpenSession()) + using (s.BeginTransaction()) + { + s.Query().Delete(); + s.Query().Delete(); + s.Query().Delete(); + s.GetCurrentTransaction().Commit(); + } + } + + [Test] + public void TestSQLProjectionWithDuplicateAliases() + { + using(ISession s = OpenSession()) + using(ITransaction t = s.BeginTransaction()) + { + Course course = new Course(); + course.CourseCode = "HIB"; + course.Description = "Hibernate Training"; + s.Save(course); + + Student gavin = new Student(); + gavin.Name = "Gavin King"; + gavin.StudentNumber = 667; + s.Save(gavin); + + Student xam = new Student(); + xam.Name = "Max Rydahl Andersen"; + xam.StudentNumber = 101; + s.Save(xam); + + Enrolment enrolment = new Enrolment(); + enrolment.Course = course; + enrolment.CourseCode = course.CourseCode; + enrolment.Semester = 1; + enrolment.Year = 1999; + enrolment.Student = xam; + enrolment.StudentNumber = xam.StudentNumber; + xam.Enrolments.Add(enrolment); + s.Save(enrolment); + + enrolment = new Enrolment(); + enrolment.Course = course; + enrolment.CourseCode = course.CourseCode; + enrolment.Semester = 3; + enrolment.Year = 1998; + enrolment.Student = gavin; + enrolment.StudentNumber = gavin.StudentNumber; + gavin.Enrolments.Add(enrolment); + s.Save(enrolment); + t.Commit(); + } + + using (var s = OpenSession()) + { + Student student = null; + var subquery = QueryOver.Of(() => student) + .And( + Expression.Sql("{e}.studentId = 667 and {student}.studentId = 667")).Select(Projections.Id()); + + var uniqueResult = s.CreateCriteria(typeof(Student), "student") + .Add(Subqueries.Exists(subquery.DetachedCriteria)) + .AddOrder(Order.Asc("Name")) + .CreateCriteria("Enrolments", "e") + .AddOrder(Order.Desc("Year")) + .AddOrder(Order.Desc("Semester")) + .CreateCriteria("Course", "c") + .AddOrder(Order.Asc("Description")) + .SetProjection( + Projections.SqlProjection( + "{alias}.studentId as studentNumber, {e}.Semester as semester," + + " {c}.CourseCode as courseCode, {c}.Description as descr", + new string[] {"studentNumber", "semester", "courseCode", "descr"}, + new[] + { + TypeFactory.HeuristicType(typeof(long)), + TypeFactory.HeuristicType(typeof(short)), + TypeFactory.HeuristicType(typeof(string)), + TypeFactory.HeuristicType(typeof(string)), + })) + .UniqueResult(); + + Assert.That(uniqueResult, Is.Not.Null); + } + + using (var s = OpenSession()) + using (s.BeginTransaction()) + { + s.Query().Delete(); + s.Query().Delete(); + s.Query().Delete(); + s.GetCurrentTransaction().Commit(); + } + } + [Test] public void CloningProjectionsTest() { diff --git a/src/NHibernate/Criterion/ICriteriaQuery.cs b/src/NHibernate/Criterion/ICriteriaQuery.cs index 7113e6676b4..a5714c1623c 100644 --- a/src/NHibernate/Criterion/ICriteriaQuery.cs +++ b/src/NHibernate/Criterion/ICriteriaQuery.cs @@ -1,11 +1,29 @@ using System.Collections.Generic; using NHibernate.Engine; +using NHibernate.Loader.Criteria; using NHibernate.Param; using NHibernate.SqlCommand; using NHibernate.Type; namespace NHibernate.Criterion { + //TODO 6.0: Add to ICriteriaQuery interface + internal static class CriteriaQueryExtensions + { + /// + /// Substitute the SQL aliases in template. + /// + public static SqlString RenderSQLAliases(this ICriteriaQuery criteriaQuery, SqlString sqlTemplate) + { + if (criteriaQuery is CriteriaQueryTranslator translator) + { + return translator.RenderSQLAliases(sqlTemplate); + } + + return sqlTemplate; + } + } + /// /// An instance of is passed to criterion, /// order and projection instances when actually compiling and @@ -80,4 +98,4 @@ public interface ICriteriaQuery Parameter CreateSkipParameter(int value); Parameter CreateTakeParameter(int value); } -} \ No newline at end of file +} diff --git a/src/NHibernate/Criterion/SQLCriterion.cs b/src/NHibernate/Criterion/SQLCriterion.cs index 06df9db44a3..093a165cfe4 100644 --- a/src/NHibernate/Criterion/SQLCriterion.cs +++ b/src/NHibernate/Criterion/SQLCriterion.cs @@ -9,6 +9,7 @@ namespace NHibernate.Criterion /// /// An that creates a SQLExpression. /// The string {alias} will be replaced by the alias of the root entity. + /// Criteria aliases can also be used: "{a}.Value + {bc}.Value". /// /// /// This allows for database specific Expressions at the cost of needing to @@ -42,7 +43,7 @@ public override SqlString ToSqlString(ICriteria criteria, ICriteriaQuery criteri parameters[paramPos++].BackTrack = parameter.BackTrack; } } - return _sql.Replace("{alias}", criteriaQuery.GetSQLAlias(criteria)); + return criteriaQuery.RenderSQLAliases(_sql).Replace("{alias}", criteriaQuery.GetSQLAlias(criteria)); } public override TypedValue[] GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery) diff --git a/src/NHibernate/Criterion/SQLProjection.cs b/src/NHibernate/Criterion/SQLProjection.cs index ce1d9c39448..ca538a21b61 100644 --- a/src/NHibernate/Criterion/SQLProjection.cs +++ b/src/NHibernate/Criterion/SQLProjection.cs @@ -1,7 +1,6 @@ using System; using NHibernate.SqlCommand; using NHibernate.Type; -using NHibernate.Util; namespace NHibernate.Criterion { @@ -9,6 +8,7 @@ namespace NHibernate.Criterion /// /// A SQL fragment. The string {alias} will be replaced by the alias of the root entity. + /// Criteria aliases can also be used: "{a}.Value + {bc}.Value". /// [Serializable] public sealed class SQLProjection : IProjection @@ -37,15 +37,17 @@ internal SQLProjection(string sql, string groupBy, string[] columnAliases, IType public SqlString ToSqlString(ICriteria criteria, int loc, ICriteriaQuery criteriaQuery) { - //SqlString result = new SqlString(criteriaQuery.GetSQLAlias(criteria)); - //result.Replace(sql, "{alias}"); - //return result; - return new SqlString(sql?.Replace("{alias}", criteriaQuery.GetSQLAlias(criteria))); + return GetSqlString(criteria, criteriaQuery, sql); } public SqlString ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) { - return new SqlString(groupBy?.Replace("{alias}", criteriaQuery.GetSQLAlias(criteria))); + return GetSqlString(criteria, criteriaQuery, groupBy); + } + + private SqlString GetSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery, string sqlTemplate) + { + return criteriaQuery.RenderSQLAliases(new SqlString(sqlTemplate)).Replace("{alias}", criteriaQuery.GetSQLAlias(criteria)); } public override string ToString() diff --git a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs index 682dc73e084..5f315951413 100644 --- a/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs +++ b/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs @@ -863,6 +863,23 @@ public string GetEntityName(ICriteria subcriteria, string propertyName) return GetEntityName(subcriteria); } + /// + /// Substitute the SQL aliases in template. + /// + public SqlString RenderSQLAliases(SqlString sqlTemplate) + { + var result = criteriaSQLAliasMap + .Where(p => !string.IsNullOrEmpty(p.Key.Alias)) + .Aggregate(sqlTemplate, (current, p) => current.Replace("{" + p.Key.Alias + "}", p.Value)); + + if (outerQueryTranslator != null) + { + return outerQueryTranslator.RenderSQLAliases(result); + } + + return result; + } + public string GetSQLAlias(ICriteria criteria, string propertyName) { if (StringHelper.IsNotRoot(propertyName, out var root))