diff --git a/ChangeLog/6.0.13_dev.txt b/ChangeLog/6.0.13_dev.txt index 797c999732..d2dd5f7a16 100644 --- a/ChangeLog/6.0.13_dev.txt +++ b/ChangeLog/6.0.13_dev.txt @@ -1,2 +1,4 @@ [main] Fixed certain cases of bad translation of casts via 'as' operator in LINQ queries [main] Addressed certain issues of translation connected with comparison with local entity instace within LINQ queries +[main] Fixed rare issues of incorrect translation of filtered index expressions including conditional expressions +[postgresql] Fixed issue of incorrect translation of contitional expressions including comparison with nullable fields \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests.Framework/NUnitFrameworkExtensions/RequireProviderAttributes.cs b/Orm/Xtensive.Orm.Tests.Framework/NUnitFrameworkExtensions/RequireProviderAttributes.cs new file mode 100644 index 0000000000..921bb82ab8 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests.Framework/NUnitFrameworkExtensions/RequireProviderAttributes.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using NUnit.Framework.Interfaces; + +namespace Xtensive.Orm.Tests +{ + /// + /// Base attribute for test storage requirement + /// + public abstract class RequireProvderAttribute : Attribute, ITestAction + { + private Version minVersion = null; + private Version maxVersion = null; + + /// + /// Gets or sets Minimal version that required for test. + /// If not set, version check is not applied + /// + public virtual string MinVersion + { + get { return (minVersion == null) ? null : minVersion.ToString(); } + set { + if (value == null) + minVersion = null; + else if (!Version.TryParse(value, out minVersion)) + throw new ArgumentException("Not a version string", nameof(value)); + } + } + + /// + /// Gets or sets Maximal version that required for test. + /// If not set, version check is not applied. + /// + public virtual string MaxVersion + { + get { return (maxVersion == null) ? null : maxVersion.ToString(); } + set { + if (value == null) + maxVersion = null; + else if (!Version.TryParse(value, out maxVersion)) + throw new ArgumentException("Not a version string", nameof(value)); + } + } + + protected abstract StorageProvider RequiredProviders { get; } + + public ActionTargets Targets => ActionTargets.Test; + + public void AfterTest(ITest test) + { + Require.ProviderIs(RequiredProviders); + if (minVersion != null) + Require.ProviderVersionAtLeast(minVersion); + if (maxVersion != null) + Require.ProviderVersionAtMost(maxVersion); + } + + public void BeforeTest(ITest test) { } + } + + [AttributeUsage(AttributeTargets.Method)] + public class RequireSqlServerAttribute : RequireProvderAttribute + { + protected override StorageProvider RequiredProviders => StorageProvider.SqlServer; + } + + [AttributeUsage(AttributeTargets.Method)] + public class RequirePostgreSqlAttribute : RequireProvderAttribute + { + protected override StorageProvider RequiredProviders => StorageProvider.PostgreSql; + } + + [AttributeUsage(AttributeTargets.Method)] + public class RequireMySqlAttribute : RequireProvderAttribute + { + protected override StorageProvider RequiredProviders => StorageProvider.MySql; + } + + [AttributeUsage(AttributeTargets.Method)] + public class RequireFirebirdAttribute : RequireProvderAttribute + { + protected override StorageProvider RequiredProviders => StorageProvider.Firebird; + } + + [AttributeUsage(AttributeTargets.Method)] + public class RequireOracleSqlAttribute : RequireProvderAttribute + { + protected override StorageProvider RequiredProviders => StorageProvider.Oracle; + } + + [AttributeUsage(AttributeTargets.Method)] + public class RequireSqliteAttribute : RequireProvderAttribute + { + protected override StorageProvider RequiredProviders => StorageProvider.Sqlite; + } + + [AttributeUsage(AttributeTargets.Method)] + public class RequireSeveralProvidersAttribute : RequireProvderAttribute + { + private readonly StorageProvider providers; + + protected override StorageProvider RequiredProviders => providers; + + public override string MinVersion + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override string MaxVersion + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public RequireSeveralProvidersAttribute(StorageProvider allowedProviders) + { + providers = allowedProviders; + } + } +} diff --git a/Orm/Xtensive.Orm.Tests/Issues/IssueJira0802_PostgreOrderByWithConditionalIssue.cs b/Orm/Xtensive.Orm.Tests/Issues/IssueJira0802_PostgreOrderByWithConditionalIssue.cs new file mode 100644 index 0000000000..18880a6dbf --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Issues/IssueJira0802_PostgreOrderByWithConditionalIssue.cs @@ -0,0 +1,1526 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. +// Created by: Alexey Kulakov +// Created: 2024.01.17 + +using System; +using System.Linq; +using NUnit.Framework; +using Xtensive.Core; +using Xtensive.Orm.Configuration; +using Xtensive.Orm.Tests.Issues.IssueJira0802_PostgreOrderByWithConditionalIssueModel; + +namespace Xtensive.Orm.Tests.Issues +{ + public sealed class IssueJira0802_PostgreOrderByWithConditionalIssue : AutoBuildTest + { + private Key sharedFlowKey; + + public Session Session { get; set; } + + public TransactionScope Transaction { get; set; } + + public override void TestFixtureSetUp() + { + base.TestFixtureSetUp(); + Session = Domain.OpenSession(); + } + + public override void TestFixtureTearDown() + { + Session?.Dispose(); + base.TestFixtureTearDown(); + } + + [SetUp] + public void SetUp() + { + Transaction = Session.OpenTransaction(); + } + + [TearDown] + public void TearDown() + { + Transaction?.Dispose(); + } + + protected override void CheckRequirements() => Require.ProviderIs(StorageProvider.PostgreSql); + + protected override DomainConfiguration BuildConfiguration() + { + var config = base.BuildConfiguration(); + config.Types.Register(typeof(MesObject).Assembly, typeof(MesObject).Namespace); + config.UpgradeMode = DomainUpgradeMode.Recreate; + return config; + } + + protected override void PopulateData() + { + using (var session = Domain.OpenSession()) + using (var transaction = session.OpenTransaction()) { + var sharedFlow = new LogisticFlow(session, 1000); + sharedFlowKey = sharedFlow.Key; + + _ = new PickingProductRequirement(session, 10) { + Quantity = new DimensionalField(session, 36), + InventoryAction = new InventoryAction(session, 100, sharedFlow, "a") + }; + + _ = new PickingProductRequirement(session, 20) { + Quantity = new DimensionalField(session, 35), + InventoryAction = new InventoryAction(session, 200, sharedFlow, "b") + }; + + _ = new PickingProductRequirement(session, 30) { + Quantity = new DimensionalField(session, 34), + InventoryAction = new InventoryAction(session, 300, sharedFlow, "a") + }; + + _ = new PickingProductRequirement(session, 40) { + Quantity = new DimensionalField(session, 34), + InventoryAction = new InventoryAction(session, 400, new LogisticFlow(session, 1100), null) + }; + + transaction.Complete(); + } + } + + + [Test] + public void ConditionalExprByEntityInOrderBy() + { + var sharedFlow = Session.Query.Single(sharedFlowKey); + + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.LogisticFlow == sharedFlow ? margin : 1)) + }) + .OrderBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.LogisticFlow == sharedFlow ? margin : 1)) + }) + .OrderBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.GreaterThan(0)); + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Skip(1).All(a => a.V2 < 40), Is.False); + Assert.That(results.Skip(1).All(a => a.V2 > 40 && a.V2 < 75), Is.True); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNumberInOrderBy1() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID > 100 ? margin : 1)) + }) + .OrderBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID > 100 ? margin : 1)) + }) + .OrderBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Skip(1).All(a => a.V2 < 40), Is.False); + Assert.That(results.Skip(1).All(a => a.V2 > 40 && a.V2 < 75), Is.True); + Assert.That(results[0].V2, Is.LessThan(40)); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNumberInOrderBy2() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID == 100 ? margin : 1)) + }) + .OrderBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID == 100 ? margin : 1)) + }) + .OrderBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Take(3).All(a => a.V2 < 40), Is.True); + Assert.That(results.Skip(3).All(a => a.V2 > 40 && a.V2 < 75), Is.True); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInOrderBy1() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == null ? margin : 1)) + }) + .OrderBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == null ? margin : 1)) + }) + .OrderBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Take(3).All(a => a.V2 < 40), Is.True); + Assert.That(results[3].V2, Is.GreaterThan(40)); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInOrderBy2() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField != null ? margin : 1)) + }) + .OrderBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField != null ? margin : 1)) + }) + .OrderBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Skip(1).All(a => a.V2 > 40), Is.True); + Assert.That(results[0].V2, Is.LessThan(40)); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInOrderBy3() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "a" ? margin : 1)) + }) + .OrderBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "a" ? margin : 1)) + }) + .OrderBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Take(2).All(a => a.V2 < 40), Is.True); + Assert.That(results.Skip(2).All(a => a.V2 > 40 && a.V2 < 75), Is.True); + Assert.That(results[0].V2, Is.LessThan(40)); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInOrderBy4() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "a" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .OrderBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "a" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .OrderBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Take(2).All(a => a.V2 < 40), Is.True); + Assert.That(results.Skip(2).All(a => a.V2 > 40), Is.True); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInOrderBy5() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "b" ? margin : 1)) + }) + .OrderBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "b" ? margin : 1)) + }) + .OrderBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Take(3).All(a => a.V2 < 40), Is.True); + Assert.That(results.Skip(3).All(a => a.V2 > 40 && a.V2 < 75), Is.True); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInOrderBy6() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "b" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .OrderBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "b" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .OrderBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Take(1).All(a => a.V2 < 40), Is.True); + Assert.That(results.Skip(1).All(a => a.V2 > 40), Is.True); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByEntityInOrderByDesc() + { + var sharedFlow = Session.Query.Single(sharedFlowKey); + + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.LogisticFlow == sharedFlow ? margin : 1)) + }) + .OrderByDescending(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.LogisticFlow == sharedFlow ? margin : 1)) + }) + .OrderByDescending(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Skip(3).All(a => a.V2 < 40), Is.True); + Assert.That(results.Take(3).All(a => a.V2 > 40), Is.True); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNumberInOrderByDesc2() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID > 100 ? margin : 1)) + }) + .OrderByDescending(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID > 100 ? margin : 1)) + }) + .OrderByDescending(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Skip(3).All(a => a.V2 < 40), Is.True); + Assert.That(results.Take(3).All(a => a.V2 > 40), Is.True); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNumberInOrderByDesc3() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID == 100 ? margin : 1)) + }) + .OrderByDescending(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID == 100 ? margin : 1)) + }) + .OrderByDescending(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Skip(1).All(a => a.V2 < 40), Is.True); + Assert.That(results.Take(1).All(a => a.V2 > 40), Is.True); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInOrderByDesc1() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == null ? margin : 1)) + }) + .OrderByDescending(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == null ? margin : 1)) + }) + .OrderByDescending(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Take(1).All(a => a.V2 > 40), Is.True); + Assert.That(results.Skip(1).All(a => a.V2 < 40), Is.True); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInOrderByDesc2() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField != null ? margin : 1)) + }) + .OrderByDescending(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField != null ? margin : 1)) + }) + .OrderByDescending(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Take(3).All(a => a.V2 > 40), Is.True); + Assert.That(results.Skip(3).All(a => a.V2 < 40), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInOrderByDesc3() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "a" ? margin : 1)) + }) + .OrderByDescending(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "a" ? margin : 1)) + }) + .OrderByDescending(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Skip(2).All(a => a.V2 < 40), Is.True); + Assert.That(results.Take(2).All(a => a.V2 > 40), Is.True); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInOrderByDesc4() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "a" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .OrderByDescending(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "a" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .OrderByDescending(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Take(2).All(a => a.V2 > 40), Is.True); + Assert.That(results.Skip(2).All(a => a.V2 < 40), Is.True); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInOrderByDesc5() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "b" ? margin : 1)) + }) + .OrderByDescending(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "b" ? margin : 1)) + }) + .OrderByDescending(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Take(1).All(a => a.V2 > 40), Is.True); + Assert.That(results.Skip(1).All(a => a.V2 < 40), Is.True); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInOrderByDesc6() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "b" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .OrderByDescending(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "b" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .OrderByDescending(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Skip(3).All(a => a.V2 < 40), Is.True); + Assert.That(results.Take(3).All(a => a.V2 > 40), Is.True); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByEntityInWhere() + { + var sharedFlow = Session.Query.Single(sharedFlowKey); + + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.LogisticFlow == sharedFlow ? margin : 1)) + }) + .Where(t => t.V2 > 40) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.LogisticFlow == sharedFlow ? margin : 1)) + }) + .Where(t => t.V2 > 40) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(3)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.All(a => a.V2 > 40), Is.True); + } + + [Test] + public void ConditionalExprByNumberInWhere1() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID > 100 ? margin : 1)) + }) + .Where(t => t.V2 > 40) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID > 100 ? margin : 1)) + }) + .Where(t => t.V2 > 40) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(3)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.All(a => a.V2 > 40), Is.True); + } + + [Test] + public void ConditionalExprByNumberInWhere2() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID == 100 ? margin : 1)) + }) + .Where(t => t.V2 > 40) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID == 100 ? margin : 1)) + }) + .Where(t => t.V2 > 40) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(1)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.All(a => a.V2 > 40), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInWhere1() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == null ? margin : 1)) + }) + .Where(t => t.V2 > 40) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == null ? margin : 1)) + }) + .Where(t => t.V2 > 40) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(1)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.All(a => a.V2 > 40), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInWhere2() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField != null ? margin : 1)) + }) + .Where(t => t.V2 > 40) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField != null ? margin : 1)) + }) + .Where(t => t.V2 > 40) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(3)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.All(a => a.V2 > 40), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInWhere3() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "a" ? margin : 1)) + }) + .Where(t => t.V2 > 40) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "a" ? margin : 1)) + }) + .Where(t => t.V2 > 40) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(2)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.All(a => a.V2 > 40), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInWhere4() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "a" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .Where(t => t.V2 > 40) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "a" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .Where(t => t.V2 > 40) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(2)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.All(a => a.V2 > 40), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInWhere5() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "b" ? margin : 1)) + }) + .Where(t => t.V2 > 40) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "b" ? margin : 1)) + }) + .Where(t => t.V2 > 40) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(1)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.All(a => a.V2 > 40), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInWhere6() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "b" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .Where(t => t.V2 > 40) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "b" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .Where(t => t.V2 > 40) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(3)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.All(a => a.V2 > 40), Is.True); + } + + [Test] + public void ConditionalExprByEntityInGroupBy() + { + var sharedFlow = Session.Query.Single(sharedFlowKey); + + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.LogisticFlow == sharedFlow ? margin : 1)) + }) + .GroupBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.LogisticFlow == sharedFlow ? margin : 1)) + }) + .GroupBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + Assert.That(results.Length, Is.EqualTo(expected.Length)); + + Assert.That(results.Select(r => r.Key).Count(a => a < 40), Is.EqualTo(1)); + Assert.That(results.Select(r => r.Key).Count(a => a > 40), Is.EqualTo(3)); + } + + [Test] + public void ConditionalExprByNumberInGroupBy1() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID > 100 ? margin : 1)) + }) + .GroupBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID > 100 ? margin : 1)) + }) + .GroupBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(3)); + Assert.That(results.Length, Is.EqualTo(expected.Length)); + + Assert.That(results.Select(r => r.Key).Count(a => a < 40), Is.EqualTo(1)); + Assert.That(results.Select(r => r.Key).Count(a => a > 40), Is.EqualTo(2)); + } + + [Test] + public void ConditionalExprByNumberInGroupBy2() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID == 100 ? margin : 1)) + }) + .GroupBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID == 100 ? margin : 1)) + }) + .GroupBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(3)); + Assert.That(results.Length, Is.EqualTo(expected.Length)); + + Assert.That(results.Select(r => r.Key).Count(a => a < 40), Is.EqualTo(2)); + Assert.That(results.Select(r => r.Key).Count(a => a > 40), Is.EqualTo(1)); + } + + [Test] + public void ConditionalExprByNullableFieldInGroupBy1() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == null ? margin : 1)) + }) + .GroupBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == null ? margin : 1)) + }) + .GroupBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + Assert.That(results.Length, Is.EqualTo(expected.Length)); + + Assert.That(results.Select(r => r.Key).Count(a => a < 40), Is.EqualTo(3)); + Assert.That(results.Select(r => r.Key).Count(a => a > 40), Is.EqualTo(1)); + } + + [Test] + public void ConditionalExprByNullableFieldInGroupBy2() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField != null ? margin : 1)) + }) + .GroupBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField != null ? margin : 1)) + }) + .GroupBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + Assert.That(results.Length, Is.EqualTo(expected.Length)); + + Assert.That(results.Select(r => r.Key).Count(a => a < 40), Is.EqualTo(1)); + Assert.That(results.Select(r => r.Key).Count(a => a > 40), Is.EqualTo(3)); + } + + [Test] + public void ConditionalExprByNullableFieldInGroupBy3() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "a" ? margin : 1)) + }) + .GroupBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "a" ? margin : 1)) + }) + .GroupBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + Assert.That(results.Length, Is.EqualTo(expected.Length)); + + Assert.That(results.Select(r => r.Key).Count(a => a < 40), Is.EqualTo(2)); + Assert.That(results.Select(r => r.Key).Count(a => a > 40), Is.EqualTo(2)); + } + + [Test] + public void ConditionalExprByNullableFieldInGroupBy4() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "a" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .GroupBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "a" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .GroupBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + Assert.That(results.Length, Is.EqualTo(expected.Length)); + + Assert.That(results.Select(r => r.Key).Count(a => a < 40), Is.EqualTo(2)); + Assert.That(results.Select(r => r.Key).Count(a => a > 40), Is.EqualTo(2)); + } + + [Test] + public void ConditionalExprByNullableFieldInGroupBy5() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "b" ? margin : 1)) + }) + .GroupBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "b" ? margin : 1)) + }) + .GroupBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(3)); + Assert.That(results.Length, Is.EqualTo(expected.Length)); + + Assert.That(results.Select(r => r.Key).Count(a => a < 40), Is.EqualTo(2)); + Assert.That(results.Select(r => r.Key).Count(a => a > 40), Is.EqualTo(1)); + } + + [Test] + public void ConditionalExprByNullableFieldInGroupBy6() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "b" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .GroupBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "b" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .GroupBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(3)); + Assert.That(results.Length, Is.EqualTo(expected.Length)); + + Assert.That(results.Select(r => r.Key).Count(a => a < 40), Is.EqualTo(1)); + Assert.That(results.Select(r => r.Key).Count(a => a > 40), Is.EqualTo(2)); + } + + [Test] + public void ConditionalExprByEntityInSum() + { + var sharedFlow = Session.Query.Single(sharedFlowKey); + + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.LogisticFlow == sharedFlow ? margin : 1)) + }) + .Sum(t => t.V2); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.LogisticFlow == sharedFlow ? margin : 1)) + }) + .Sum(t => t.V2); + + Assert.That(results, Is.EqualTo(expected)); + } + + [Test] + public void ConditionalExprByNumberInSum1() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID > 100 ? margin : 1)) + }) + .Sum(t => t.V2); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID > 100 ? margin : 1)) + }) + .Sum(t => t.V2); + + Assert.That(results, Is.EqualTo(expected)); + } + + [Test] + public void ConditionalExprByNumberInSum2() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID == 100 ? margin : 1)) + }) + .Sum(t => t.V2); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.ID == 100 ? margin : 1)) + }) + .Sum(t => t.V2); + + Assert.That(results, Is.EqualTo(expected)); + } + + [Test] + public void ConditionalExprByNullableFieldInSum1() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == null ? margin : 1)) + }) + .Sum(t => t.V2); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == null ? margin : 1)) + }) + .Sum(t => t.V2); + + Assert.That(results, Is.EqualTo(expected)); + } + + [Test] + public void ConditionalExprByNullableFieldInSum2() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField != null ? margin : 1)) + }) + .Sum(t => t.V2); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField != null ? margin : 1)) + }) + .Sum(t => t.V2); + + Assert.That(results, Is.EqualTo(expected)); + } + + [Test] + public void ConditionalExprByNullableFieldInSum3() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "a" ? margin : 1)) + }) + .Sum(t => t.V2); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "a" ? margin : 1)) + }) + .Sum(t => t.V2); + + Assert.That(results, Is.EqualTo(expected)); + } + + [Test] + public void ConditionalExprByNullableFieldInSum4() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "a" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .Sum(t => t.V2); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "a" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .Sum(t => t.V2); + + Assert.That(results, Is.EqualTo(expected)); + } + + [Test] + public void ConditionalExprByNullableFieldInSum5() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "b" ? margin : 1)) + }) + .Sum(t => t.V2); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "b" ? margin : 1)) + }) + .Sum(t => t.V2); + + Assert.That(results, Is.EqualTo(expected)); + } + + [Test] + public void ConditionalExprByNullableFieldInSum6() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "b" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .Sum(t => t.V2); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (int) (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "b" || p.InventoryAction.NullableField == null ? margin : 1)) + }) + .Sum(t => t.V2); + + Assert.That(results, Is.EqualTo(expected)); + } + + [Test] + public void ConditionalExprByEntityInInclude() + { + var sharedFlow = Session.Query.Single(sharedFlowKey); + + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (p.Quantity.NormalizedValue * (p.InventoryAction.LogisticFlow == sharedFlow ? margin : 1)).In(40, 70, 72) + }) + .OrderBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (p.Quantity.NormalizedValue * (p.InventoryAction.LogisticFlow == sharedFlow ? margin : 1)).In(40, 70, 72) + }) + .OrderBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Count(p => p.V2 == true), Is.EqualTo(2)); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNumberInInclude1() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (p.Quantity.NormalizedValue * (p.InventoryAction.ID > 100 ? margin : 1)).In(40, 70, 72) + }) + .OrderBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (p.Quantity.NormalizedValue * (p.InventoryAction.ID > 100 ? margin : 1)).In(40, 70, 72) + }) + .OrderBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Count(p => p.V2 == true), Is.EqualTo(1)); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNumberInInclude2() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (p.Quantity.NormalizedValue * (p.InventoryAction.ID == 100 ? margin : 1)).In(40, 70, 72) // 100 -> 36 + }) + .OrderBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All() + .Select(p => new { + V2 = (p.Quantity.NormalizedValue * (p.InventoryAction.ID == 100 ? margin : 1)).In(40, 70, 72) // 100 -> 36 + }) + .OrderBy(t => t.V2) + .ToArray(); + + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Count(p => p.V2 == true), Is.EqualTo(1)); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInInclude1() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == null ? margin : 1)).In(40, 68, 72) + }) + .OrderBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == null ? margin : 1)).In(40, 68, 72) + }) + .OrderBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Count(p => p.V2 == true), Is.EqualTo(1)); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInInclude2() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField != null ? margin : 1)).In(40, 70, 72) + }) + .OrderBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField != null ? margin : 1)).In(40, 70, 72) + }) + .OrderBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Count(p => p.V2 == true), Is.EqualTo(2)); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInInclude3() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "a" ? margin : 1)).In(40, 70, 72) + }) + .OrderBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "a" ? margin : 1)).In(40, 70, 72) + }) + .OrderBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Count(p => p.V2 == true), Is.EqualTo(1)); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInInclude4() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "a" || p.InventoryAction.NullableField == null ? margin : 1)).In(40, 70, 72) + }) + .OrderBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "a" || p.InventoryAction.NullableField == null ? margin : 1)).In(40, 70, 72) + }) + .OrderBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Count(p => p.V2 == true), Is.EqualTo(1)); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInInclude5() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "b" ? margin : 1)).In(40, 70, 72) + }) + .OrderBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (p.Quantity.NormalizedValue * (p.InventoryAction.NullableField == "b" ? margin : 1)).In(40, 70, 72) + }) + .OrderBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Count(p => p.V2 == true), Is.EqualTo(1)); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + [Test] + public void ConditionalExprByNullableFieldInInclude6() + { + var margin = 2; + var results = Session.Query.All() + .Select(p => new { + V2 = (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "b" || p.InventoryAction.NullableField == null ? margin : 1)).In(40, 70, 72) + }) + .OrderBy(t => t.V2) + .ToArray(); + + var expected = Session.Query.All().AsEnumerable() + .Select(p => new { + V2 = (p.Quantity.NormalizedValue * + (p.InventoryAction.NullableField != "b" || p.InventoryAction.NullableField == null ? margin : 1)).In(40, 70, 72) + }) + .OrderBy(t => t.V2) + .ToArray(); + + Assert.That(expected.Length, Is.EqualTo(4)); + + Assert.That(results.Length, Is.EqualTo(expected.Length)); + Assert.That(results.Count(p => p.V2 == true), Is.EqualTo(1)); + Assert.That(results.SequenceEqual(expected), Is.True); + } + + + //private void PrintExpected(TItem[] items, Func accessor) + //{ + // Console.WriteLine(); + // Console.WriteLine("Expected order of values:"); + + // foreach (var item in items) { + // Console.WriteLine(accessor(item)); + // } + //} + + private static void CommandExecutingEventHandler(object sender, DbCommandEventArgs e) + { + var command = e.Command; + var commandText = command.CommandText; + Console.WriteLine("No Modifications SQL Text:"); + Console.WriteLine(commandText); + var parameters = command.Parameters; + + Console.Write(" Parameters: "); + for (int i = 0, count = parameters.Count; i < count; i++) { + var parameter = parameters[0]; + Console.WriteLine($"{parameter.ParameterName} = {parameter.Value.ToString()}"); + } + } + } +} + + +namespace Xtensive.Orm.Tests.Issues.IssueJira0802_PostgreOrderByWithConditionalIssueModel +{ + [HierarchyRoot] + public class InventoryAction : MesObject + { + [Field] + public LogisticFlow LogisticFlow { get; set; } + + [Field(Length = 50)] + public string NullableField { get; set; } + + public InventoryAction(Session Session, int id, LogisticFlow logisticFlow, string nullableValue) + : base(Session, id) + { + LogisticFlow = logisticFlow; + NullableField = nullableValue; + } + } + + [HierarchyRoot] + public class LogisticFlow : MesObject + { + public LogisticFlow(Session Session, int id) + : base(Session, id) + { + } + } + + [HierarchyRoot] + public class PickingProductRequirement : MesObject + { + [Field] + public DimensionalField Quantity { get; set; } + + [Field] + public InventoryAction InventoryAction { get; set; } + + public PickingProductRequirement(Session Session, int id) + : base(Session, id) + { + } + } + + public class DimensionalField : Structure + { + [Field] + public int NormalizedValue { get; private set; } + + public DimensionalField(Session Session, int nValue) + : base(Session) + { + NormalizedValue = nValue; + } + } + + public abstract class MesObject : Entity + { + [Field, Key] + public int ID { get; private set; } + + protected MesObject(Session Session, int id) + : base(Session, id) + { + } + } +} \ No newline at end of file diff --git a/Orm/Xtensive.Orm.Tests/Storage/PartialIndexTest.cs b/Orm/Xtensive.Orm.Tests/Storage/PartialIndexTest.cs index 2a8bf8cbd5..aafd9540f0 100644 --- a/Orm/Xtensive.Orm.Tests/Storage/PartialIndexTest.cs +++ b/Orm/Xtensive.Orm.Tests/Storage/PartialIndexTest.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2011-2021 Xtensive LLC. +// Copyright (C) 2011-2024 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. // Created by: Denis Krjuchkov @@ -14,6 +14,7 @@ using Xtensive.Orm.Model; using Xtensive.Orm.Providers; using Xtensive.Orm.Tests.Storage.PartialIndexTestModel; +using NUnit.Framework.Interfaces; namespace Xtensive.Orm.Tests.Storage.PartialIndexTestModel { @@ -70,9 +71,9 @@ public class SimpleFilterWithProperty : TestBase } [HierarchyRoot, Index(nameof(Target), Filter = nameof(Index))] - public class FilterOnReferenceField : TestBase + public class FilterOnReferenceField1 : TestBase { - public static Expression> Index() => + public static Expression> Index() => test => test.Target != null; [Field] @@ -80,15 +81,87 @@ public static Expression> Index() => } [HierarchyRoot, Index(nameof(Target), Filter = nameof(Index))] - public class FilterOnComplexReferenceField : TestBase + public class FilterOnReferenceField2 : TestBase { - public static Expression> Index() => + public static Expression> Index() => + test => test.Target == null; + + [Field] + public TargetEntity Target { get; set; } + } + + [HierarchyRoot, Index(nameof(Target), Filter = nameof(Index))] + public class FilterOnReferenceField3 : TestBase + { + public static Expression> Index() => + test => test.Target == test.Alien; + + [Field] + public TargetEntity Target { get; set; } + + [Field] + public TargetEntity Alien { get; set; } + } + + [HierarchyRoot, Index(nameof(Target), Filter = nameof(Index))] + public class FilterOnReferenceField4 : TestBase + { + public static Expression> Index() => + test => test.Target != test.Alien; + + [Field] + public TargetEntity Target { get; set; } + + [Field] + public TargetEntity Alien { get; set; } + } + + [HierarchyRoot, Index(nameof(Target), Filter = nameof(Index))] + public class FilterOnComplexReferenceField1 : TestBase + { + public static Expression> Index() => test => test.Target != null; [Field] public ComplexTargetEntity Target { get; set; } } + [HierarchyRoot, Index(nameof(Target), Filter = nameof(Index))] + public class FilterOnComplexReferenceField2 : TestBase + { + public static Expression> Index() => + test => test.Target == null; + + [Field] + public ComplexTargetEntity Target { get; set; } + } + + [HierarchyRoot, Index(nameof(Target), Filter = nameof(Index))] + public class FilterOnComplexReferenceField3 : TestBase + { + public static Expression> Index() => + test => test.Target == test.Alien; + + [Field] + public ComplexTargetEntity Target { get; set; } + + [Field] + public ComplexTargetEntity Alien { get; set; } + } + + [HierarchyRoot, Index(nameof(Target), Filter = nameof(Index))] + public class FilterOnComplexReferenceField4 : TestBase + { + public static Expression> Index() => + test => test.Target != test.Alien; + + [Field] + public ComplexTargetEntity Target { get; set; } + + [Field] + public ComplexTargetEntity Alien { get; set; } + } + [HierarchyRoot, Index(nameof(Target), Filter = nameof(Index))] public class FilterOnReferenceIdField : TestBase { @@ -164,6 +237,16 @@ public class ContainsOperatorSupport : TestBase public string TestField { get; set; } } + [HierarchyRoot, Index(nameof(TestField), Filter = nameof(Index))] + public class ConditionalExpressionSuport : TestBase + { + public static Expression> Index => + test => test.Id == 100 ? test.TestField.Contains("hello") : false; + + [Field] + public string TestField { get; set; } + } + [HierarchyRoot, Index(nameof(TestField), Filter = nameof(More), Name = "MoreIndex"), Index(nameof(TestField), Filter = nameof(Less), Name = "LessIndex")] @@ -520,10 +603,28 @@ private void AssertBuildFailure(params Type[] entities) public void SimpleFilterWithPropertyTest() => AssertBuildSuccess(typeof(SimpleFilterWithProperty)); [Test] - public void FilterOnReferenceFieldTest() => AssertBuildSuccess(typeof(FilterOnReferenceField)); + public void FilterOnReferenceFieldTest1() => AssertBuildSuccess(typeof(FilterOnReferenceField1)); [Test] - public void FilterOnComplexReferenceFieldTest() => AssertBuildSuccess(typeof(FilterOnComplexReferenceField)); + public void FilterOnReferenceFieldTest2() => AssertBuildSuccess(typeof(FilterOnReferenceField2)); + + [Test] + public void FilterOnReferenceFieldTest3() => AssertBuildFailure(typeof(FilterOnReferenceField3)); + + [Test] + public void FilterOnReferenceFieldTest4() => AssertBuildFailure(typeof(FilterOnReferenceField4)); + + [Test] + public void FilterOnComplexReferenceFieldTest1() => AssertBuildSuccess(typeof(FilterOnComplexReferenceField1)); + + [Test] + public void FilterOnComplexReferenceFieldTest2() => AssertBuildSuccess(typeof(FilterOnComplexReferenceField2)); + + [Test] + public void FilterOnComplexReferenceFieldTest3() => AssertBuildFailure(typeof(FilterOnComplexReferenceField3)); + + [Test] + public void FilterOnComplexReferenceFieldTest4() => AssertBuildFailure(typeof(FilterOnComplexReferenceField4)); [Test] public void FilterOnReferenceFieldIdTest() => AssertBuildSuccess(typeof(FilterOnReferenceIdField)); @@ -543,6 +644,9 @@ private void AssertBuildFailure(params Type[] entities) [Test] public void ContainsOperatorSupportTest() => AssertBuildSuccess(typeof(ContainsOperatorSupport)); + [Test, RequirePostgreSql] + public void ConditionalOperationSupportTest() => AssertBuildSuccess(typeof(ConditionalExpressionSuport)); + [Test] public void DoubleIndexWithNameTest() => AssertBuildSuccess(typeof(DoubleIndexWithName)); @@ -564,12 +668,34 @@ private void AssertBuildFailure(params Type[] entities) [Test] public void EnumFieldFilterTest() => AssertBuildSuccess(typeof(EnumFieldFilter)); - [Test] - public void ValidateTest() + [Test, RequirePostgreSql] + public void ValidateTestForPgSql() + { + var types = typeof(TestBase).Assembly + .GetTypes() + .Where(type => type.Namespace == typeof(TestBase).Namespace + && type != typeof(InheritanceClassTable) + && type != typeof(FilterOnReferenceField3) + && type != typeof(FilterOnReferenceField4) + && type != typeof(FilterOnComplexReferenceField3) + && type != typeof(FilterOnComplexReferenceField4)) + .ToList(); + BuildDomain(types, DomainUpgradeMode.Recreate); + BuildDomain(types, DomainUpgradeMode.Validate); + } + + [Test, RequireSqlServer] + public void ValidateTestForSqlServer() { var types = typeof(TestBase).Assembly .GetTypes() - .Where(type => type.Namespace == typeof(TestBase).Namespace && type != typeof(InheritanceClassTable)) + .Where(type => type.Namespace == typeof(TestBase).Namespace + && type != typeof(InheritanceClassTable) + && type != typeof(ConditionalExpressionSuport) + && type != typeof(FilterOnReferenceField3) + && type != typeof(FilterOnReferenceField4) + && type != typeof(FilterOnComplexReferenceField3) + && type != typeof(FilterOnComplexReferenceField4)) .ToList(); BuildDomain(types, DomainUpgradeMode.Recreate); BuildDomain(types, DomainUpgradeMode.Validate); diff --git a/Orm/Xtensive.Orm/Orm/Building/Builders/PartialIndexFilterBuilder.cs b/Orm/Xtensive.Orm/Orm/Building/Builders/PartialIndexFilterBuilder.cs index fd08eededc..0d2482b618 100644 --- a/Orm/Xtensive.Orm/Orm/Building/Builders/PartialIndexFilterBuilder.cs +++ b/Orm/Xtensive.Orm/Orm/Building/Builders/PartialIndexFilterBuilder.cs @@ -88,6 +88,8 @@ protected override Expression VisitBinary(BinaryExpression b) return BuildEntityCheck(field, b.NodeType); if (entityAccessMap.TryGetValue(right, out field) && IsNull(left)) return BuildEntityCheck(field, b.NodeType); + if (entityAccessMap.TryGetValue(left, out var _) && entityAccessMap.TryGetValue(right, out var _)) + throw UnableToTranslate(b, Strings.ComparisonOfTwoEntityFieldsIsNotSupported); return base.VisitBinary(b); diff --git a/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.Helpers.cs b/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.Helpers.cs index 49cc1b674d..8b843abaaf 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.Helpers.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.Helpers.cs @@ -15,7 +15,7 @@ namespace Xtensive.Orm.Providers { - partial class ExpressionProcessor + internal partial class ExpressionProcessor { private readonly static Type ObjectType = typeof(object); private readonly static Type BooleanType = typeof(bool); @@ -157,7 +157,7 @@ private SqlExpression TryTranslateBinaryExpressionSpecialCases(Expression expres private SqlExpression TryTranslateEqualitySpecialCases(SqlExpression left, SqlExpression right) { - if (right.NodeType==SqlNodeType.Null || emptyStringIsNull && IsEmptyStringLiteral(right)) + if (right.NodeType==SqlNodeType.Null || EmptyStringIsNull && IsEmptyStringLiteral(right)) return SqlDml.IsNull(left); object id = null; @@ -173,7 +173,7 @@ private SqlExpression TryTranslateEqualitySpecialCases(SqlExpression left, SqlEx private SqlExpression TryTranslateInequalitySpecialCases(SqlExpression left, SqlExpression right) { - if (right.NodeType==SqlNodeType.Null || emptyStringIsNull && IsEmptyStringLiteral(right)) + if (right.NodeType==SqlNodeType.Null || EmptyStringIsNull && IsEmptyStringLiteral(right)) return SqlDml.IsNotNull(left); object id = null; diff --git a/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.cs b/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.cs index fc50bf5519..1c6bb185b5 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/Expressions/ExpressionProcessor.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2008-2021 Xtensive LLC. +// Copyright (C) 2008-2024 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. // Created by: Alexey Kochetov @@ -22,34 +22,51 @@ namespace Xtensive.Orm.Providers { internal sealed partial class ExpressionProcessor : ExpressionVisitor { + [Flags] + private enum ProcessorOptions + { + None = 0, + FixBooleanExpressions = 1 << 0, + PreferCaseOverVariant = 1 << 1, + EmptyStringIsNull = 1 << 2, + DateTimeEmulation = 1 << 3, + DateTimeOffsetEmulation = 1 << 4, + SpecialByteArrayComparison = 1 << 5 + } + private static readonly SqlExpression SqlFalse = SqlDml.Literal(false); private static readonly SqlExpression SqlTrue = SqlDml.Literal(true); + private readonly SqlCompiler compiler; + private readonly LambdaExpression lambda; private readonly StorageDriver driver; private readonly BooleanExpressionConverter booleanExpressionConverter; private readonly IMemberCompilerProvider memberCompilerProvider; private readonly IReadOnlyList[] sourceColumns; private readonly ExpressionEvaluator evaluator; private readonly ParameterExtractor parameterExtractor; - private readonly LambdaExpression lambda; - private readonly HashSet bindings; - private readonly List activeParameters; - private readonly Dictionary> sourceMapping; - private readonly SqlCompiler compiler; + private readonly ProviderInfo providerInfo; + private readonly ProcessorOptions options; + private readonly List activeParameters + = new List(); + private readonly Dictionary> sourceMapping + = new Dictionary>(); private readonly Dictionary bindingsWithIdentity = new Dictionary(); - private readonly List otherBindings = new List(); - - private readonly bool fixBooleanExpressions; - private readonly bool emptyStringIsNull; - private readonly bool dateTimeEmulation; - private readonly bool dateTimeOffsetEmulation; - private readonly bool specialByteArrayComparison; - private readonly ProviderInfo providerInfo; + private readonly List otherBindings + = new List(); private bool executed; + private bool FixBooleanExpressions => (options & ProcessorOptions.FixBooleanExpressions) !=0; + private bool PreferCaseOverVariant => options.HasFlag(ProcessorOptions.PreferCaseOverVariant); + private bool EmptyStringIsNull => (options & ProcessorOptions.EmptyStringIsNull) != 0; + private bool DateTimeEmulation => (options & ProcessorOptions.DateTimeEmulation) != 0; + private bool DateTimeOffsetEmulation => (options & ProcessorOptions.DateTimeOffsetEmulation) != 0; + private bool SpecialByteArrayComparison => (options & ProcessorOptions.SpecialByteArrayComparison) != 0; + + public SqlExpression Translate() { if (executed) @@ -101,12 +118,12 @@ private SqlExpression VisitParameterAccess(Expression e, bool smartNull) SqlExpression result; if (optimizeBooleanParameter) { result = SqlDml.Variant(binding, SqlFalse, SqlTrue); - if (fixBooleanExpressions) + if (FixBooleanExpressions) result = booleanExpressionConverter.IntToBoolean(result); } else { result = binding.ParameterReference; - if (type==typeof(bool) && fixBooleanExpressions) + if (type == typeof(bool) && FixBooleanExpressions) result = booleanExpressionConverter.IntToBoolean(result); else if (typeMapping.ParameterCastRequired) result = SqlDml.Cast(result, typeMapping.MapType()); @@ -151,9 +168,9 @@ private SqlExpression VisitCast(UnaryExpression cast, SqlExpression operand) if (IsEnumUnderlyingType(sourceType, targetType) || IsEnumUnderlyingType(targetType, sourceType)) return operand; // Special case for boolean cast - if (fixBooleanExpressions && IsBooleanExpression(cast.Operand)) { + if (FixBooleanExpressions && IsBooleanExpression(cast.Operand)) { var result = SqlDml.Case(); - result.Add(operand, 1); + _ = result.Add(operand, 1); result.Else = 0; operand = result; } @@ -177,7 +194,7 @@ protected override SqlExpression VisitBinary(BinaryExpression expression) expression.NodeType == ExpressionType.Equal || expression.NodeType == ExpressionType.NotEqual; - var isBooleanFixRequired = fixBooleanExpressions + var isBooleanFixRequired = FixBooleanExpressions && (isEqualityCheck || expression.NodeType == ExpressionType.Coalesce) && (IsBooleanExpression(expression.Left) || IsBooleanExpression(expression.Right)); @@ -214,7 +231,7 @@ protected override SqlExpression VisitBinary(BinaryExpression expression) } //handle SQLite DateTime comparsion - if (dateTimeEmulation + if (DateTimeEmulation && left.NodeType != SqlNodeType.Null && right.NodeType != SqlNodeType.Null && IsComparisonExpression(expression) @@ -224,7 +241,7 @@ protected override SqlExpression VisitBinary(BinaryExpression expression) } //handle SQLite DateTimeOffset comparsion - if (dateTimeOffsetEmulation + if (DateTimeOffsetEmulation && left.NodeType != SqlNodeType.Null && right.NodeType != SqlNodeType.Null && IsComparisonExpression(expression) @@ -234,7 +251,7 @@ protected override SqlExpression VisitBinary(BinaryExpression expression) } //handle Oracle special syntax of BLOB comparison - if (specialByteArrayComparison + if (SpecialByteArrayComparison && (IsExpressionOf(expression.Left, typeof(byte[])) || IsExpressionOf(expression.Left, typeof(byte[])))) { var comparison = BuildByteArraySyntaxComparison(left, right); left = comparison.left; @@ -323,43 +340,46 @@ protected override SqlExpression VisitConditional(ConditionalExpression expressi var check = Visit(expression.Test); var ifTrue = Visit(expression.IfTrue); var ifFalse = Visit(expression.IfFalse); - SqlContainer container = ifTrue as SqlContainer; - if (container!=null) - ifTrue = TryUnwrapEnum(container); - container = ifFalse as SqlContainer; - if (container!=null) - ifFalse = TryUnwrapEnum(container); - var boolCheck = fixBooleanExpressions + + if (ifTrue is SqlContainer ifTrueContainer) + ifTrue = TryUnwrapEnum(ifTrueContainer); + if (ifFalse is SqlContainer ifFalseContainer) + ifFalse = TryUnwrapEnum(ifFalseContainer); + + var fixExpressions = FixBooleanExpressions; + + var boolCheck = fixExpressions ? booleanExpressionConverter.BooleanToInt(check) : check; var varCheck = boolCheck as SqlVariant; - if (!varCheck.IsNullReference()) + + if (!PreferCaseOverVariant && !varCheck.IsNullReference()) { return SqlDml.Variant(varCheck.Id, ifFalse, ifTrue); - if (fixBooleanExpressions && IsBooleanExpression(expression)) { - var c = SqlDml.Case(); - c[check] = booleanExpressionConverter.BooleanToInt(ifTrue); - c.Else = booleanExpressionConverter.BooleanToInt(ifFalse); - return booleanExpressionConverter.IntToBoolean(c); + } + var @case = SqlDml.Case(); + if (fixExpressions && IsBooleanExpression(expression)) { + @case[check] = booleanExpressionConverter.BooleanToInt(ifTrue); + @case.Else = booleanExpressionConverter.BooleanToInt(ifFalse); + return booleanExpressionConverter.IntToBoolean(@case); } else { - var c = SqlDml.Case(); - c[check] = ifTrue; - c.Else = ifFalse; - return c; + @case[check] = ifTrue; + @case.Else = ifFalse; + return @case; } } protected override SqlExpression VisitConstant(ConstantExpression expression) { if (expression.Value==null) - return fixBooleanExpressions && expression.Type==typeof (bool?) + return FixBooleanExpressions && expression.Type==typeof (bool?) ? booleanExpressionConverter.IntToBoolean(SqlDml.Null) : SqlDml.Null; var type = expression.Type; if (type==typeof (object)) type = expression.Value.GetType(); type = type.StripNullable(); - if (fixBooleanExpressions && type==typeof (bool)) { + if (FixBooleanExpressions && type == typeof(bool)) { var literal = SqlDml.Literal((bool) expression.Value); return booleanExpressionConverter.IntToBoolean(literal); } @@ -404,7 +424,7 @@ private SqlExpression VisitTupleAccess(MethodCallExpression tupleAccess) var queryRef = sourceMapping[(ParameterExpression) tupleAccess.Object]; result = queryRef[columnIndex]; } - if (fixBooleanExpressions && IsBooleanExpression(tupleAccess)) + if (FixBooleanExpressions && IsBooleanExpression(tupleAccess)) result = booleanExpressionConverter.IntToBoolean(result); return result; } @@ -464,39 +484,47 @@ private SqlExpression TryUnwrapEnum(SqlContainer container) // Constructors - public ExpressionProcessor( - LambdaExpression lambda, HandlerAccessor handlers, SqlCompiler compiler, params IReadOnlyList[] sourceColumns) + public ExpressionProcessor(LambdaExpression lambda, + HandlerAccessor handlers, + SqlCompiler compiler, + in bool preferCaseOverVariant, + params IReadOnlyList[] sourceColumns) { ArgumentValidator.EnsureArgumentNotNull(lambda, "lambda"); ArgumentValidator.EnsureArgumentNotNull(handlers, "handlers"); ArgumentValidator.EnsureArgumentNotNull(sourceColumns, "sourceColumns"); + if (lambda.Parameters.Count != sourceColumns.Length) + throw Exceptions.InternalError(Strings.ExParametersCountIsNotSameAsSourceColumnListsCount, OrmLog.Instance); + if (sourceColumns.Any(list => list.Any(c => c.IsNullReference()))) + throw Exceptions.InternalError(Strings.ExSourceColumnListContainsNullValues, OrmLog.Instance); + this.compiler = compiler; // This might be null, check before use! this.lambda = lambda; this.sourceColumns = sourceColumns; providerInfo = handlers.ProviderInfo; driver = handlers.StorageDriver; - - fixBooleanExpressions = !providerInfo.Supports(ProviderFeatures.FullFeaturedBooleanExpressions); - emptyStringIsNull = providerInfo.Supports(ProviderFeatures.TreatEmptyStringAsNull); - dateTimeEmulation = providerInfo.Supports(ProviderFeatures.DateTimeEmulation); - dateTimeOffsetEmulation = providerInfo.Supports(ProviderFeatures.DateTimeOffsetEmulation); memberCompilerProvider = handlers.DomainHandler.GetMemberCompilerProvider(); - specialByteArrayComparison = providerInfo.ProviderName.Equals(WellKnown.Provider.Oracle); - bindings = new HashSet(); - activeParameters = new List(); evaluator = new ExpressionEvaluator(lambda); parameterExtractor = new ParameterExtractor(evaluator); - if (fixBooleanExpressions) + options = ProcessorOptions.None; + if (!providerInfo.Supports(ProviderFeatures.FullFeaturedBooleanExpressions)) { + options |= ProcessorOptions.FixBooleanExpressions; booleanExpressionConverter = new BooleanExpressionConverter(driver); - if (lambda.Parameters.Count!=sourceColumns.Length) - throw Exceptions.InternalError(Strings.ExParametersCountIsNotSameAsSourceColumnListsCount, OrmLog.Instance); - if (sourceColumns.Any(list => list.Any(c => c.IsNullReference()))) - throw Exceptions.InternalError(Strings.ExSourceColumnListContainsNullValues, OrmLog.Instance); - sourceMapping = new Dictionary>(); + } + if (providerInfo.Supports(ProviderFeatures.TreatEmptyStringAsNull)) + options |= ProcessorOptions.EmptyStringIsNull; + if (providerInfo.Supports(ProviderFeatures.DateTimeEmulation)) + options |= ProcessorOptions.DateTimeEmulation; + if (providerInfo.Supports(ProviderFeatures.DateTimeOffsetEmulation)) + options |= ProcessorOptions.DateTimeOffsetEmulation; + if (providerInfo.ProviderName.Equals(WellKnown.Provider.Oracle)) + options |= ProcessorOptions.SpecialByteArrayComparison; + if (preferCaseOverVariant) + options |= ProcessorOptions.PreferCaseOverVariant; } } } diff --git a/Orm/Xtensive.Orm/Orm/Providers/PartialIndexFilterCompiler.cs b/Orm/Xtensive.Orm/Orm/Providers/PartialIndexFilterCompiler.cs index 0d0f5ea5c5..4bf594fb14 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/PartialIndexFilterCompiler.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/PartialIndexFilterCompiler.cs @@ -1,11 +1,12 @@ -// Copyright (C) 2013 Xtensive LLC -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2013-2024 Xtensive LLC +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. // Created by: Denis Krjuchkov // Created: 2013.07.18 using System.Linq; using Xtensive.Orm.Model; +using Xtensive.Core; using Xtensive.Sql; using Xtensive.Sql.Dml; using Xtensive.Sql.Model; @@ -16,15 +17,18 @@ internal sealed class PartialIndexFilterCompiler { public string Compile(HandlerAccessor handlers, IndexInfo index) { - var table = SqlDml.TableRef(CreateStubTable(index.ReflectedType.MappingName, index.Filter.Fields.Count)); + var filter = index.Filter; + var fieldsCount = filter.Fields.Count; + + var table = SqlDml.TableRef(CreateStubTable(index.ReflectedType.MappingName, fieldsCount)); // Translation of ColumnRefs without alias seems broken, use original name as alias. - var columns = index.Filter.Fields + var columns = filter.Fields .Select(field => field.Column.Name) .Select((name, i) => SqlDml.ColumnRef(table.Columns[i], name)) .Cast() - .ToList(); + .ToList(fieldsCount); - var processor = new ExpressionProcessor(index.Filter.Expression, handlers, null, columns); + var processor = new ExpressionProcessor(filter.Expression, handlers, null, true, columns); var fragment = SqlDml.Fragment(processor.Translate()); var result = handlers.StorageDriver.Compile(fragment).GetCommandText(); return result; diff --git a/Orm/Xtensive.Orm/Orm/Providers/SqlCompiler.Helpers.cs b/Orm/Xtensive.Orm/Orm/Providers/SqlCompiler.Helpers.cs index aa00b8f55a..7fb873ee41 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/SqlCompiler.Helpers.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/SqlCompiler.Helpers.cs @@ -1,4 +1,4 @@ -// Copyright (C) 2009-2020 Xtensive LLC. +// Copyright (C) 2009-2024 Xtensive LLC. // This code is distributed under MIT license terms. // See the License.txt file in the project root for more information. // Created by: Denis Krjuchkov @@ -60,10 +60,10 @@ protected SqlProvider CreateProvider(SqlSelect statement, IEnumerable name; - protected Pair> ProcessExpression(LambdaExpression le, + protected Pair> ProcessExpression(LambdaExpression le, in bool preferCaseOverVariant, params IReadOnlyList[] sourceColumns) { - var processor = new ExpressionProcessor(le, Handlers, this, sourceColumns); + var processor = new ExpressionProcessor(le, Handlers, this, preferCaseOverVariant, sourceColumns); var result = new Pair>( processor.Translate(), processor.GetBindings()); return result; diff --git a/Orm/Xtensive.Orm/Orm/Providers/SqlCompiler.cs b/Orm/Xtensive.Orm/Orm/Providers/SqlCompiler.cs index 3c3547ec1f..41133eb315 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/SqlCompiler.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/SqlCompiler.cs @@ -111,7 +111,7 @@ protected override SqlProvider VisitCalculate(CalculateProvider provider) var sourceColumns = ExtractColumnExpressions(sqlSelect); var allBindings = EnumerableUtils.Empty; foreach (var column in provider.CalculatedColumns) { - var result = ProcessExpression(column.Expression, sourceColumns); + var result = ProcessExpression(column.Expression, true, sourceColumns); var predicate = result.First; var bindings = result.Second; if (column.Type.StripNullable()==typeof (bool)) @@ -150,7 +150,7 @@ protected override SqlProvider VisitFilter(FilterProvider provider) var query = ExtractSqlSelect(provider, source); var sourceColumns = ExtractColumnExpressions(query); - var result = ProcessExpression(provider.Predicate, sourceColumns); + var result = ProcessExpression(provider.Predicate, true, sourceColumns); var predicate = result.First; var bindings = result.Second; @@ -254,7 +254,7 @@ protected override SqlProvider VisitPredicateJoin(PredicateJoinProvider provider var joinType = provider.JoinType==JoinType.LeftOuter ? SqlJoinType.LeftOuterJoin : SqlJoinType.InnerJoin; - var result = ProcessExpression(provider.Predicate, leftExpressions, rightExpressions); + var result = ProcessExpression(provider.Predicate, false, leftExpressions, rightExpressions); var joinExpression = result.First; var bindings = result.Second; diff --git a/Orm/Xtensive.Orm/Strings.Designer.cs b/Orm/Xtensive.Orm/Strings.Designer.cs index 1d2b359c23..b81e1655a9 100644 --- a/Orm/Xtensive.Orm/Strings.Designer.cs +++ b/Orm/Xtensive.Orm/Strings.Designer.cs @@ -259,6 +259,15 @@ internal static string Comma { } } + /// + /// Looks up a localized string similar to Comparison of two entity fields is not supported.. + /// + internal static string ComparisonOfTwoEntityFieldsIsNotSupported { + get { + return ResourceManager.GetString("ComparisonOfTwoEntityFieldsIsNotSupported", resourceCulture); + } + } + /// /// Looks up a localized string similar to ComparisonRule({0}, {1}). /// diff --git a/Orm/Xtensive.Orm/Strings.resx b/Orm/Xtensive.Orm/Strings.resx index 11131ec5cc..ca63bb877b 100644 --- a/Orm/Xtensive.Orm/Strings.resx +++ b/Orm/Xtensive.Orm/Strings.resx @@ -3473,4 +3473,7 @@ Error: {1} {0} expressions with constant values of {1} type are not supported. + + Comparison of two entity fields is not supported. + \ No newline at end of file