diff --git a/src/NHibernate.Test/Async/Criteria/ConditionalProjectionTest.cs b/src/NHibernate.Test/Async/Criteria/ConditionalProjectionTest.cs new file mode 100644 index 00000000000..39192ae2708 --- /dev/null +++ b/src/NHibernate.Test/Async/Criteria/ConditionalProjectionTest.cs @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using System; +using System.Linq; +using NHibernate.Criterion; +using NUnit.Framework; + +namespace NHibernate.Test.Criteria +{ + using System.Threading.Tasks; + [TestFixture] + public class ConditionalProjectionTestAsync : TestCase + { + protected override string MappingsAssembly => "NHibernate.Test"; + + protected override string[] Mappings => new [] {"Criteria.Enrolment.hbm.xml"}; + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Save(new Student {StudentNumber = 6L, Name = "testa"}); + session.Save(new Student {StudentNumber = 5L, Name = "testz"}); + session.Save(new Student {StudentNumber = 4L, Name = "test1"}); + session.Save(new Student {StudentNumber = 3L, Name = "test2"}); + session.Save(new Student {StudentNumber = 2L, Name = "test998"}); + session.Save(new Student {StudentNumber = 1L, Name = "test999"}); + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = Sfi.OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + transaction.Commit(); + } + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return !TestDialect.HasBrokenTypeInferenceOnSelectedParameters; + } + + [Test] + public async Task UsingMultiConditionalsAsync() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + // when Name = "testa" then 1 ... + var orderOfNames = new Tuple[] + { + System.Tuple.Create("test1", "1"), + System.Tuple.Create("testz", "2"), + System.Tuple.Create("test2", "3"), + System.Tuple.Create("testa", "4") + }; + + var criterionProjections = + orderOfNames + .Select( + x => new ConditionalProjectionCase( + Restrictions.Eq(nameof(Student.Name), x.Item1), + Projections.Constant(x.Item2))) + .ToArray(); + + // ... else 99 + var elseProjection = Projections.Constant("99"); + + var conditionalsProjection = Projections.Conditional(criterionProjections, elseProjection); + + var order = Order.Asc(conditionalsProjection); + + var criteria = session.CreateCriteria(typeof(Student)).AddOrder(order); + + var actuals = await (criteria.ListAsync()); + + Assert.That(actuals.Count, Is.GreaterThanOrEqualTo(orderOfNames.Length)); + for (var i = 0; i < orderOfNames.Length; i++) + { + var expected = orderOfNames[i]; + var actual = actuals[i]; + + Assert.That(actual.Name, Is.EqualTo(expected.Item1)); + } + } + } + } +} diff --git a/src/NHibernate.Test/Criteria/ConditionalProjectionTest.cs b/src/NHibernate.Test/Criteria/ConditionalProjectionTest.cs new file mode 100644 index 00000000000..89d4d5e060c --- /dev/null +++ b/src/NHibernate.Test/Criteria/ConditionalProjectionTest.cs @@ -0,0 +1,90 @@ +using System; +using System.Linq; +using NHibernate.Criterion; +using NUnit.Framework; + +namespace NHibernate.Test.Criteria +{ + [TestFixture] + public class ConditionalProjectionTest : TestCase + { + protected override string MappingsAssembly => "NHibernate.Test"; + + protected override string[] Mappings => new [] {"Criteria.Enrolment.hbm.xml"}; + + protected override void OnSetUp() + { + using (var session = OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.Save(new Student {StudentNumber = 6L, Name = "testa"}); + session.Save(new Student {StudentNumber = 5L, Name = "testz"}); + session.Save(new Student {StudentNumber = 4L, Name = "test1"}); + session.Save(new Student {StudentNumber = 3L, Name = "test2"}); + session.Save(new Student {StudentNumber = 2L, Name = "test998"}); + session.Save(new Student {StudentNumber = 1L, Name = "test999"}); + transaction.Commit(); + } + } + + protected override void OnTearDown() + { + using (var session = Sfi.OpenSession()) + using (var transaction = session.BeginTransaction()) + { + session.CreateQuery("delete from System.Object").ExecuteUpdate(); + transaction.Commit(); + } + } + + protected override bool AppliesTo(Dialect.Dialect dialect) + { + return !TestDialect.HasBrokenTypeInferenceOnSelectedParameters; + } + + [Test] + public void UsingMultiConditionals() + { + using (var session = OpenSession()) + using (session.BeginTransaction()) + { + // when Name = "testa" then 1 ... + var orderOfNames = new Tuple[] + { + System.Tuple.Create("test1", "1"), + System.Tuple.Create("testz", "2"), + System.Tuple.Create("test2", "3"), + System.Tuple.Create("testa", "4") + }; + + var criterionProjections = + orderOfNames + .Select( + x => new ConditionalProjectionCase( + Restrictions.Eq(nameof(Student.Name), x.Item1), + Projections.Constant(x.Item2))) + .ToArray(); + + // ... else 99 + var elseProjection = Projections.Constant("99"); + + var conditionalsProjection = Projections.Conditional(criterionProjections, elseProjection); + + var order = Order.Asc(conditionalsProjection); + + var criteria = session.CreateCriteria(typeof(Student)).AddOrder(order); + + var actuals = criteria.List(); + + Assert.That(actuals.Count, Is.GreaterThanOrEqualTo(orderOfNames.Length)); + for (var i = 0; i < orderOfNames.Length; i++) + { + var expected = orderOfNames[i]; + var actual = actuals[i]; + + Assert.That(actual.Name, Is.EqualTo(expected.Item1)); + } + } + } + } +} diff --git a/src/NHibernate/Criterion/ConditionalProjection.cs b/src/NHibernate/Criterion/ConditionalProjection.cs index 1ff3a95a185..09cc421638b 100644 --- a/src/NHibernate/Criterion/ConditionalProjection.cs +++ b/src/NHibernate/Criterion/ConditionalProjection.cs @@ -1,138 +1,222 @@ +using System; +using System.Collections.Generic; +using NHibernate.Engine; +using NHibernate.SqlCommand; +using NHibernate.Type; + namespace NHibernate.Criterion { - using System; - using System.Collections.Generic; - using Engine; - using SqlCommand; - using Type; - + /// + /// Defines a "switch" projection which supports multiple "cases" ("when/then's"). + /// + /// + /// [Serializable] public class ConditionalProjection : SimpleProjection { - private readonly ICriterion criterion; - private readonly IProjection whenTrue; - private readonly IProjection whenFalse; + private readonly ConditionalProjectionCase[] _cases; + private readonly IProjection _elseProjection; + /// + /// Initializes a new instance of the class. + /// + /// The + /// The true + /// The else . public ConditionalProjection(ICriterion criterion, IProjection whenTrue, IProjection whenFalse) { - this.whenTrue = whenTrue; - this.whenFalse = whenFalse; - this.criterion = criterion; + _elseProjection = whenFalse; + _cases = new[] {new ConditionalProjectionCase(criterion, whenTrue)}; + } + + /// + /// Initializes a new instance of the class. + /// + /// The s containing and pairs. + /// The else . + public ConditionalProjection(ConditionalProjectionCase[] cases, IProjection elseProjection) + { + if (cases == null) + throw new ArgumentNullException(nameof(cases)); + if (cases.Length == 0) + throw new ArgumentException("Array should not be empty.", nameof(cases)); + + _cases = cases; + _elseProjection = elseProjection; } public override bool IsAggregate { get { - IProjection[] projections = criterion.GetProjections(); - if(projections != null) + if (_elseProjection.IsAggregate) + return true; + + foreach (var projectionCase in _cases) { - foreach (IProjection projection in projections) + if (projectionCase.Projection.IsAggregate) + return true; + + var projections = projectionCase.Criterion.GetProjections(); + if (projections != null) { - if (projection.IsAggregate) - return true; + foreach (var projection in projections) + { + if (projection.IsAggregate) + { + return true; + } + } } } - if(whenFalse.IsAggregate) - return true; - if(whenTrue.IsAggregate) + + return false; + } + } + + public override bool IsGrouped + { + get + { + if (_elseProjection.IsGrouped) return true; + + foreach (var projectionCase in _cases) + { + if (projectionCase.Projection.IsGrouped) + return true; + + var projections = projectionCase.Criterion.GetProjections(); + if (projections != null) + { + foreach (var projection in projections) + { + if (projection.IsGrouped) + return true; + } + } + } + return false; } } public override SqlString ToSqlString(ICriteria criteria, int position, ICriteriaQuery criteriaQuery) { - SqlString condition = criterion.ToSqlString(criteria, criteriaQuery); - var ifTrue = CriterionUtil.GetColumnNameAsSqlStringPart(whenTrue, criteriaQuery, criteria); - var ifFalse = CriterionUtil.GetColumnNameAsSqlStringPart(whenFalse, criteriaQuery, criteria); - return new SqlString("(case when ", condition, " then ", ifTrue, " else ", ifFalse, " end) as ", - GetColumnAlias(position)); + var sqlBuilder = new SqlStringBuilder(5 + (_cases.Length * 4)); + + sqlBuilder.Add("(case"); + + foreach (var projectionCase in _cases) + { + sqlBuilder.Add(" when "); + sqlBuilder.Add(projectionCase.Criterion.ToSqlString(criteria, criteriaQuery)); + sqlBuilder.Add(" then "); + sqlBuilder.AddObject(CriterionUtil.GetColumnNameAsSqlStringPart(projectionCase.Projection, criteriaQuery, criteria)); + } + + sqlBuilder.Add(" else "); + sqlBuilder.AddObject(CriterionUtil.GetColumnNameAsSqlStringPart(_elseProjection, criteriaQuery, criteria)); + + sqlBuilder.Add(" end) as "); + sqlBuilder.Add(GetColumnAlias(position)); + + return sqlBuilder.ToSqlString(); } public override IType[] GetTypes(ICriteria criteria, ICriteriaQuery criteriaQuery) { - IType[] trueTypes = whenTrue.GetTypes(criteria, criteriaQuery); - IType[] falseTypes = whenFalse.GetTypes(criteria, criteriaQuery); + var elseTypes = _elseProjection.GetTypes(criteria, criteriaQuery); - bool areEqual = trueTypes.Length == falseTypes.Length; - if (areEqual) + for (var i = 0; i < _cases.Length; i++) { - for (int i = 0; i < trueTypes.Length; i++) + var subsequentTypes = _cases[i].Projection.GetTypes(criteria, criteriaQuery); + if (!AreTypesEqual(elseTypes, subsequentTypes)) { - if(trueTypes[i].ReturnedClass != falseTypes[i].ReturnedClass) - { - areEqual = false; - break; - } + string msg = "All projections must return the same types." + Environment.NewLine + + "But Else projection returns: [" + string.Join(", ", elseTypes) + "] " + Environment.NewLine + + "And When projection " + i + " returns: [" + string.Join(", ", subsequentTypes) + "]"; + + throw new HibernateException(msg); } } - if(areEqual == false) - { - string msg = "Both true and false projections must return the same types."+ Environment.NewLine + - "But True projection returns: ["+string.Join(", ", trueTypes) +"] "+ Environment.NewLine+ - "And False projection returns: ["+string.Join(", ", falseTypes)+ "]"; - throw new HibernateException(msg); + return elseTypes; + } + + public override TypedValue[] GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery) + { + var typedValues = new List(); + + foreach (var projectionCase in _cases) + { + typedValues.AddRange(projectionCase.Criterion.GetTypedValues(criteria, criteriaQuery)); + typedValues.AddRange(projectionCase.Projection.GetTypedValues(criteria, criteriaQuery)); } - return trueTypes; + typedValues.AddRange(_elseProjection.GetTypedValues(criteria, criteriaQuery)); + + return typedValues.ToArray(); } - public override TypedValue[] GetTypedValues(ICriteria criteria, ICriteriaQuery criteriaQuery) + public override SqlString ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) { - List tv = new List(); - tv.AddRange(criterion.GetTypedValues(criteria, criteriaQuery)); - tv.AddRange(whenTrue.GetTypedValues(criteria, criteriaQuery)); - tv.AddRange(whenFalse.GetTypedValues(criteria, criteriaQuery)); - return tv.ToArray(); + var sqlBuilder = new SqlStringBuilder(); + + foreach (var projection in _cases) + { + AddToGroupedSql(sqlBuilder, projection.Criterion.GetProjections(), criteria, criteriaQuery); + AddToGroupedSql(sqlBuilder, projection.Projection, criteria, criteriaQuery); + } + + AddToGroupedSql(sqlBuilder, _elseProjection, criteria, criteriaQuery); + + // Remove last comma + if (sqlBuilder.Count >= 2) + { + sqlBuilder.RemoveAt(sqlBuilder.Count - 1); + } + + return sqlBuilder.ToSqlString(); } - public override bool IsGrouped + private static bool AreTypesEqual(IType[] types1, IType[] types2) { - get + bool areEqual = types1.Length == types2.Length; + if (!areEqual) + { + return false; + } + + for (int i = 0; i < types1.Length; i++) { - IProjection[] projections = criterion.GetProjections(); - if(projections != null) + if (types1[i].ReturnedClass != types2[i].ReturnedClass) { - foreach (IProjection projection in projections) - { - if (projection.IsGrouped) - return true; - } + return false; } - if(whenFalse.IsGrouped) - return true; - if(whenTrue.IsGrouped) - return true; - return false; } + + return true; } - public override SqlString ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery) + private void AddToGroupedSql(SqlStringBuilder sqlBuilder, IProjection[] projections, ICriteria criteria, ICriteriaQuery criteriaQuery) { - SqlStringBuilder buf = new SqlStringBuilder(); - IProjection[] projections = criterion.GetProjections(); - if(projections != null) + if (projections == null) + return; + + foreach (var projection in projections) { - foreach (IProjection proj in projections) - { - if (proj.IsGrouped) - { - buf.Add(proj.ToGroupSqlString(criteria, criteriaQuery)).Add(", "); - } - } + AddToGroupedSql(sqlBuilder, projection, criteria, criteriaQuery); } - if(whenFalse.IsGrouped) - buf.Add(whenFalse.ToGroupSqlString(criteria, criteriaQuery)).Add(", "); - if(whenTrue.IsGrouped) - buf.Add(whenTrue.ToGroupSqlString(criteria, criteriaQuery)).Add(", "); + } - if(buf.Count >= 2) + private void AddToGroupedSql(SqlStringBuilder sqlBuilder, IProjection projection, ICriteria criteria, ICriteriaQuery criteriaQuery) + { + if (projection.IsGrouped) { - buf.RemoveAt(buf.Count - 1); + sqlBuilder.Add(projection.ToGroupSqlString(criteria, criteriaQuery)); + sqlBuilder.Add(", "); } - return buf.ToSqlString(); } } } diff --git a/src/NHibernate/Criterion/ConditionalProjectionCase.cs b/src/NHibernate/Criterion/ConditionalProjectionCase.cs new file mode 100644 index 00000000000..e8fb1aaf9bf --- /dev/null +++ b/src/NHibernate/Criterion/ConditionalProjectionCase.cs @@ -0,0 +1,29 @@ +namespace NHibernate.Criterion +{ + /// + /// Defines a pair of and . + /// + public class ConditionalProjectionCase + { + /// + /// Initializes a new instance of the class. + /// + /// The . + /// The . + public ConditionalProjectionCase(ICriterion criterion, IProjection projection) + { + Criterion = criterion; + Projection = projection; + } + + /// + /// Gets the . + /// + public ICriterion Criterion { get; } + + /// + /// Gets the . + /// + public IProjection Projection { get; } + } +} diff --git a/src/NHibernate/Criterion/Projections.cs b/src/NHibernate/Criterion/Projections.cs index f152ef9459b..57aba8310f0 100644 --- a/src/NHibernate/Criterion/Projections.cs +++ b/src/NHibernate/Criterion/Projections.cs @@ -1,8 +1,8 @@ using System; using System.Linq.Expressions; +using NHibernate.Dialect.Function; using NHibernate.Impl; using NHibernate.Type; -using NHibernate.Dialect.Function; namespace NHibernate.Criterion { @@ -317,7 +317,7 @@ public static IProjection Constant(object obj) /// public static IProjection Constant(object obj, IType type) { - return new ConstantProjection(obj,type); + return new ConstantProjection(obj, type); } /// @@ -327,7 +327,7 @@ public static IProjection Constant(object obj, IType type) /// The type. /// The projections. /// - public static IProjection SqlFunction(string functionName, IType type, params IProjection [] projections) + public static IProjection SqlFunction(string functionName, IType type, params IProjection[] projections) { return new SqlFunctionProjection(functionName, type, projections); } @@ -356,6 +356,18 @@ public static IProjection Conditional(ICriterion criterion, IProjection whenTrue return new ConditionalProjection(criterion, whenTrue, whenFalse); } + /// + /// Conditionally returns one of the s depending on the s of or the . + /// This produces an switch-case expression with multiple when-then parts. + /// + /// The s which contain your s and s. + /// The else . + /// A for a switch-expression with multiple Criterions ("when") Projections ("then"). + public static IProjection Conditional(ConditionalProjectionCase[] cases, IProjection elseProjection) + { + return new ConditionalProjection(cases, elseProjection); + } + public static IProjection SubQuery(DetachedCriteria detachedCriteria) { SelectSubqueryExpression expr = new SelectSubqueryExpression(detachedCriteria); @@ -417,7 +429,7 @@ public static PropertyProjection Group(Expression> expression { return Projections.GroupProperty(ExpressionProcessor.FindMemberExpression(expression.Body)); } - + /// /// A grouping property projection /// @@ -538,10 +550,10 @@ public static IProjection Select(Expression> expr internal static IProjection ProcessConcat(MethodCallExpression methodCallExpression) { - NewArrayExpression args = (NewArrayExpression)methodCallExpression.Arguments[0]; + NewArrayExpression args = (NewArrayExpression) methodCallExpression.Arguments[0]; IProjection[] projections = new IProjection[args.Expressions.Count]; - for (var i=0; i