From 1a99c0ff71b51d97a5f7e1122d07c821cc86920b Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Thu, 31 Oct 2024 19:26:02 +0500 Subject: [PATCH 1/8] Improved tests of string operations' compilers Also moved String members compiler tests from Linq.SelectTest to proper class --- Orm/Xtensive.Orm.Tests/Linq/SelectTest.cs | 112 ---- .../Providers/Sql/StringOperationsTest.cs | 531 ++++++++++++++++-- 2 files changed, 499 insertions(+), 144 deletions(-) diff --git a/Orm/Xtensive.Orm.Tests/Linq/SelectTest.cs b/Orm/Xtensive.Orm.Tests/Linq/SelectTest.cs index 0c218e418d..92a9454fee 100644 --- a/Orm/Xtensive.Orm.Tests/Linq/SelectTest.cs +++ b/Orm/Xtensive.Orm.Tests/Linq/SelectTest.cs @@ -768,118 +768,6 @@ where invoice.InvoiceId > 0 && invoice.InvoiceId < 50 } } - [Test] - public void SelectStringIndexerTest() - { - var result = - Session.Query.All() - .Select(c => new { - String = c.CustomerId, - Char0 = c.Email[0], - Char1 = c.Email[1], - Char2 = c.Email[2], - Char3 = c.Email[3], - Char4 = c.Email[4], - }) - .ToArray() - .OrderBy(item => item.String) - .ToArray(); - var expected = Customers - .Select(c => new { - String = c.CustomerId, - Char0 = c.Email[0], - Char1 = c.Email[1], - Char2 = c.Email[2], - Char3 = c.Email[3], - Char4 = c.Email[4], - }) - .OrderBy(item => item.String) - .ToArray(); - Assert.AreEqual(expected.Length, result.Length); - for (var i = 0; i < expected.Length; i++) { - Assert.AreEqual(expected[0].String, result[0].String); - Assert.AreEqual(expected[0].Char0, result[0].Char0); - Assert.AreEqual(expected[0].Char1, result[0].Char1); - Assert.AreEqual(expected[0].Char2, result[0].Char2); - Assert.AreEqual(expected[0].Char3, result[0].Char3); - Assert.AreEqual(expected[0].Char4, result[0].Char4); - } - } - - [Test] - public void SelectIndexOfTest() - { - var _char = 'A'; - var result = - Session.Query.All() - .Select(c => new { - String = c.FirstName, - IndexOfChar = c.FirstName.IndexOf(_char), - IndexOfCharStart = c.FirstName.IndexOf(_char, 1), - IndexOfCharStartCount = c.FirstName.IndexOf(_char, 1, 1), - IndexOfString = c.FirstName.IndexOf(_char.ToString()), - IndexOfStringStart = c.FirstName.IndexOf(_char.ToString(), 1), - IndexOfStringStartCount = c.FirstName.IndexOf(_char.ToString(), 1, 1) - }) - .ToArray() - .OrderBy(item => item.String) - .ToArray(); - var expected = Customers - .Select(c => new { - String = c.FirstName, - IndexOfChar = c.FirstName.IndexOf(_char), - IndexOfCharStart = c.FirstName.IndexOf(_char, 1), - IndexOfCharStartCount = c.FirstName.IndexOf(_char, 1, 1), - IndexOfString = c.FirstName.IndexOf(_char.ToString()), - IndexOfStringStart = c.FirstName.IndexOf(_char.ToString(), 1), - IndexOfStringStartCount = c.FirstName.IndexOf(_char.ToString(), 1, 1) - }) - .OrderBy(item => item.String) - .ToArray(); - Assert.AreEqual(expected.Length, result.Length); - for (var i = 0; i < expected.Length; i++) { - Assert.AreEqual(expected[i].String, result[i].String); - Assert.AreEqual(expected[i].IndexOfChar, result[i].IndexOfChar); - Assert.AreEqual(expected[i].IndexOfCharStart, result[i].IndexOfCharStart); - Assert.AreEqual(expected[i].IndexOfCharStartCount, result[i].IndexOfCharStartCount); - Assert.AreEqual(expected[i].IndexOfString, result[i].IndexOfString); - Assert.AreEqual(expected[i].IndexOfStringStart, result[i].IndexOfStringStart); - Assert.AreEqual(expected[i].IndexOfStringStartCount, result[i].IndexOfStringStartCount); - } - } - - [Test] - public void SelectStringContainsTest1() - { - Require.ProviderIs(StorageProvider.Sqlite | StorageProvider.SqlServer | StorageProvider.MySql); - var result = - Session.Query.All() - .Where(c => c.FirstName.Contains('L')) - .OrderBy(c => c.CustomerId) - .ToArray(); - var expected = Customers - .Where(c => c.FirstName.Contains('L') || c.FirstName.Contains('l')) - .OrderBy(c => c.CustomerId) - .ToArray(); - Assert.IsTrue(expected.SequenceEqual(result)); - } - - [Test] - public void SelectStringContainsTest2() - { - Require.ProviderIsNot(StorageProvider.Sqlite | StorageProvider.SqlServer | StorageProvider.MySql); - var result = - Session.Query.All() - .Where(c => c.FirstName.Contains('L')) - .OrderBy(c => c.CustomerId) - .ToArray(); - var expected = Customers - .Where(c => c.FirstName.Contains('L')) - .OrderBy(c => c.CustomerId) - .ToArray(); - Assert.IsTrue(expected.SequenceEqual(result)); - } - [Test] public void SelectDateTimeTimeSpanTest() { diff --git a/Orm/Xtensive.Orm.Tests/Storage/Providers/Sql/StringOperationsTest.cs b/Orm/Xtensive.Orm.Tests/Storage/Providers/Sql/StringOperationsTest.cs index 633321395f..81d71f6030 100644 --- a/Orm/Xtensive.Orm.Tests/Storage/Providers/Sql/StringOperationsTest.cs +++ b/Orm/Xtensive.Orm.Tests/Storage/Providers/Sql/StringOperationsTest.cs @@ -5,6 +5,7 @@ // Created: 2009.07.13 using NUnit.Framework; +using System; using System.Linq; using Xtensive.Orm.Configuration; using Xtensive.Orm.Providers; @@ -15,23 +16,11 @@ namespace Xtensive.Orm.Tests.Storage.Providers.Sql [TestFixture] public class StringOperationsTest : AutoBuildTest { - #region Configuration - - private bool emptyStringIsNull; + private const string StringOfWhiteSpaces = " "; - protected override DomainConfiguration BuildConfiguration() - { - var configuration = base.BuildConfiguration(); - configuration.Types.Register(typeof(X).Assembly, typeof(X).Namespace); - return configuration; - } + #region Configuration - public override void TestFixtureSetUp() - { - base.TestFixtureSetUp(); - _ = CreateSessionAndTransaction(); - emptyStringIsNull = ProviderInfo.Supports(ProviderFeatures.TreatEmptyStringAsNull); - var testValues = new[] { + private readonly string[] testValues = new[] { // test values for TrimStart, TrimEnd, Trim " :-P ", ";-)", @@ -60,15 +49,52 @@ public override void TestFixtureSetUp() "ololo]ololo", "ololoololo]", // other test values - " ", + StringOfWhiteSpaces, }; - foreach (var value in testValues) { + + + private bool emptyStringIsNull; + private bool autoTrimWhiteSpaces; + private bool whitespaceStringAsEmptyString; + + + private Session globalSession; + private TransactionScope globalTransaction; + + + protected override DomainConfiguration BuildConfiguration() + { + var configuration = base.BuildConfiguration(); + configuration.Types.Register(typeof(X).Assembly, typeof(X).Namespace); + return configuration; + } + + public override void TestFixtureSetUp() + { + base.TestFixtureSetUp(); + InitStringRules(); + + var sessionAndTransaction = CreateSessionAndTransaction(); + globalSession = sessionAndTransaction.Item1; + globalTransaction = sessionAndTransaction.Item2; + + var fStrings = emptyStringIsNull ? testValues : testValues.Append(string.Empty); + foreach (var value in fStrings) { _ = new X { FString = value }; } - if (!emptyStringIsNull) { - _ = new X { FString = string.Empty }; - } + globalSession.SaveChanges(); + } + + private void InitStringRules() + { + (emptyStringIsNull, whitespaceStringAsEmptyString, autoTrimWhiteSpaces) = + StorageProviderInfo.Instance.Provider switch { + StorageProvider.Firebird => (ProviderInfo.Supports(ProviderFeatures.TreatEmptyStringAsNull), true, true), + StorageProvider.MySql => (ProviderInfo.Supports(ProviderFeatures.TreatEmptyStringAsNull), true, false), + StorageProvider.SqlServer => (ProviderInfo.Supports(ProviderFeatures.TreatEmptyStringAsNull), true, false), + _ => (ProviderInfo.Supports(ProviderFeatures.TreatEmptyStringAsNull), false, false) + }; } #endregion @@ -76,12 +102,51 @@ public override void TestFixtureSetUp() [Test] public void LengthTest() { - var results = Session.Demand().Query.All().Select(x => new { + var results = globalSession.Query.All().Select(x => new { String = x.FString, Length = x.FString.Length }).ToList(); - foreach (var item in results) + foreach (var item in results) { Assert.AreEqual(ConvertString(item.String).Length, item.Length); + } + } + + [Test] + public void LengthServerSideTest() + { + foreach (var value in testValues) { + Assert.That(globalSession.Query.All().Where(x => x.FString == value && x.FString.Length == value.Length).Count(), + Is.EqualTo(1), $"Failed for '{value}'.Length"); + } + } + + [Test] + public void CharsTest() + { + var results = globalSession.Query.All() + .Where(x => x.Id > 5 && x.Id < 11) + .Select(x => new { + String = x.FString, + Char1 = x.FString[1], + Char2 = x.FString[2] + }) + .ToList(); + foreach (var item in results) { + Assert.AreEqual(ConvertString(item.String)[1], item.Char1); + Assert.AreEqual(ConvertString(item.String)[2], item.Char2); + } + } + + [Test] + public void CharsServerSideTest() + { + foreach (var value in testValues.Where(t => t[0] != ' ' && t.Length >= 2)) { + Assert.That(globalSession.Query.All().Where(x => x.FString == value && x.FString[0] == value[0]).Count(), + Is.EqualTo(1), $"Failed for '{value}'[0]"); + + Assert.That(globalSession.Query.All().Where(x => x.FString == value && x.FString[1] == value[1]).Count(), + Is.EqualTo(1), $"Failed for '{value}'[1]"); + } } #region Trim, TrimStart, TrimEnd @@ -89,7 +154,7 @@ public void LengthTest() [Test] public void TrimSpaceTest() { - var results = Session.Demand().Query.All() + var results = globalSession.Query.All() .Select(x => new { String = x.FString, StringTrim = x.FString.Trim(), @@ -115,11 +180,63 @@ public void TrimSpaceTest() } } + [Test] + public void TrimSpaceServerSideTest() + { + var checkForWhitespaceString = StorageProviderInfo.Instance.Provider.HasFlag(StorageProvider.Oracle) + ? (emptyStringIsNull && whitespaceStringAsEmptyString) || autoTrimWhiteSpaces + : emptyStringIsNull || autoTrimWhiteSpaces || whitespaceStringAsEmptyString; + + foreach (var value in testValues) { + var expectedValue = value == StringOfWhiteSpaces && checkForWhitespaceString + ? 2 : 1; + + var result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.Trim() == value.Trim()).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.Trim()"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.TrimStart() == value.TrimStart()).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.TrimStart()"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.TrimEnd() == value.TrimEnd()).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.TrimEnd()"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.Trim(null) == value.Trim(null)).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.Trim(null)"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.TrimStart(null) == value.TrimStart(null)).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.TrimStart(null)"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.TrimEnd(null) == value.TrimEnd(null)).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.TrimEnd(null)"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.Trim(' ') == value.Trim(' ')).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for {value}.Trim(' ')"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.TrimStart(' ') == value.TrimStart(' ')).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for {value}.TrimStart(' ')"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.TrimEnd(' ') == value.TrimEnd(' ')).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for {value}.TrimEnd(' ')"); + } + } + [Test] public void TrimOtherCharTest() { - Require.ProviderIsNot(StorageProvider.SqlServer | StorageProvider.SqlServerCe); - var results = Session.Demand().Query.All() + Require.ProviderIsNot( + StorageProvider.SqlServer | StorageProvider.SqlServerCe, + "Can't trim anything except spaces"); + + var results = globalSession.Query.All() .Select(x => new { String = x.FString, StringTrimLeadingLargePLetter = x.FString.TrimStart('P'), @@ -133,19 +250,66 @@ public void TrimOtherCharTest() } } + [Test] + public void TrimOtherCharServerSideTest() + { + Require.ProviderIsNot(StorageProvider.SqlServer | StorageProvider.SqlServerCe); + + var checkForWhitespaceString = StorageProviderInfo.Instance.Provider.HasFlag(StorageProvider.Oracle) + ? (emptyStringIsNull && whitespaceStringAsEmptyString) || autoTrimWhiteSpaces + : emptyStringIsNull || autoTrimWhiteSpaces || whitespaceStringAsEmptyString; + + foreach (var value in testValues) { + var expectedValue = value == StringOfWhiteSpaces && checkForWhitespaceString + ? 2 : 1; + + var result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.TrimStart('P') == value.TrimStart('P')).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.TrimStart('P')"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.Trim('o') == value.Trim('o')).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.Trim('o')"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.TrimEnd(')') == value.TrimEnd(')')).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.TrimEnd(')')"); + } + } + [Test] public void TrimMultipleCharsTest() { - Require.ProviderIsNot(StorageProvider.SqlServer | StorageProvider.Oracle | StorageProvider.SqlServerCe); - var results = Session.Demand().Query.All() + Require.ProviderIsNot( + StorageProvider.SqlServer | StorageProvider.Oracle | StorageProvider.SqlServerCe | StorageProvider.MySql, + "No support for trimming multiple characters"); + + var results = globalSession.Query.All() .Select(x => new { String = x.FString, StringTrimLeadingZeroAndOne = x.FString.TrimStart('0', '1'), }).ToList(); - foreach (var x in results) + foreach (var x in results) { Assert.AreEqual(ConvertString(x.String).TrimStart('0', '1'), ConvertString(x.StringTrimLeadingZeroAndOne)); + } } + [Test] + public void TrimMultipleCharsServerSideTest() + { + Require.ProviderIsNot( + StorageProvider.SqlServer | StorageProvider.Oracle | StorageProvider.SqlServerCe | StorageProvider.MySql, + "No support for trimming multiple characters"); + + foreach (var value in testValues) { + var expectedValue = value == StringOfWhiteSpaces && (emptyStringIsNull || autoTrimWhiteSpaces || whitespaceStringAsEmptyString) + ? 2 : 1; + + var result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.TrimStart('0', '1') == value.TrimStart('0', '1')).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.TrimStart('0', '1')"); + } + } #endregion @@ -154,7 +318,7 @@ public void TrimMultipleCharsTest() [Test] public void StartsWithTest() { - var result = Session.Demand().Query.All().Select(x => new { + var result = globalSession.Query.All().Select(x => new { x.Id, String = x.FString, StartsWithA = x.FString.StartsWith("A"), @@ -178,10 +342,55 @@ public void StartsWithTest() } } + [Test] + public void StartsWithServerSideTest() + { + var checkForWhitespaceString = StorageProviderInfo.Instance.Provider.HasFlag(StorageProvider.Oracle) + ? (emptyStringIsNull && whitespaceStringAsEmptyString) || autoTrimWhiteSpaces + : emptyStringIsNull || autoTrimWhiteSpaces || whitespaceStringAsEmptyString; + + foreach (var value in testValues) { + var expectedValue = value == StringOfWhiteSpaces && checkForWhitespaceString + ? 2 : 1; + + var result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.StartsWith("A") == value.StartsWith("A")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"A\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.StartsWith("%") == value.StartsWith("%")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"%\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.StartsWith("_") == value.StartsWith("_")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"_\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.StartsWith("%_") == value.StartsWith("%_")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"%_\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.StartsWith("_%") == value.StartsWith("_%")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"_%\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.StartsWith("^") == value.StartsWith("^")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"^\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.StartsWith("[") == value.StartsWith("[")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"[\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.StartsWith("]") == value.StartsWith("]")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"]\")"); + } + } + [Test] public void EndsWithTest() { - var result = Session.Demand().Query.All().Select(x => new { + var result = globalSession.Query.All().Select(x => new { x.Id, String = x.FString, EndsWithA = x.FString.EndsWith("A"), @@ -205,10 +414,55 @@ public void EndsWithTest() } } + [Test] + public void EndsWithServerSideTest() + { + var checkForWhitespaceString = StorageProviderInfo.Instance.Provider.HasFlag(StorageProvider.Oracle) + ? (emptyStringIsNull && whitespaceStringAsEmptyString) || autoTrimWhiteSpaces + : emptyStringIsNull || autoTrimWhiteSpaces || whitespaceStringAsEmptyString; + + foreach (var value in testValues) { + var expectedValue = value == StringOfWhiteSpaces && checkForWhitespaceString + ? 2 : 1; + + var result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.EndsWith("A") == value.EndsWith("A")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"A\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.EndsWith("%") == value.EndsWith("%")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"%\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.EndsWith("_") == value.EndsWith("_")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"_\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.EndsWith("%_") == value.EndsWith("%_")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"%_\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.EndsWith("_%") == value.EndsWith("_%")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"_%\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.EndsWith("^") == value.EndsWith("^")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"^\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.EndsWith("[") == value.EndsWith("[")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"[\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.EndsWith("]") == value.EndsWith("]")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"]\")"); + } + } + [Test] public void ContainsTest() { - var result = Session.Demand().Query.All().Select(x => new { + var result = globalSession.Query.All().Select(x => new { x.Id, String = x.FString, ContainsA = x.FString.Contains("A"), @@ -232,6 +486,51 @@ public void ContainsTest() } } + [Test] + public void ContainsServerSideTest() + { + var checkForWhitespaceString = StorageProviderInfo.Instance.Provider.HasFlag(StorageProvider.Oracle) + ? (emptyStringIsNull && whitespaceStringAsEmptyString) || autoTrimWhiteSpaces + : emptyStringIsNull || autoTrimWhiteSpaces || whitespaceStringAsEmptyString; + + foreach (var value in testValues) { + var expectedValue = value == StringOfWhiteSpaces && checkForWhitespaceString + ? 2 : 1; + + var result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.Contains("A") == value.Contains("A")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"A\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.Contains("%") == value.Contains("%")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"%\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.Contains("_") == value.Contains("_")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"_\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.Contains("%_") == value.Contains("%_")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"%_\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.Contains("_%") == value.Contains("_%")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"_%\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.Contains("^") == value.Contains("^")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"^\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.Contains("[") == value.Contains("[")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"[\")"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.EndsWith("]") == value.EndsWith("]")).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.StartsWith(\"]\")"); + } + } + #endregion #region PadLeft, PadRight @@ -239,7 +538,7 @@ public void ContainsTest() [Test] public void PaddingTest() { - var result = Session.Demand().Query.All().Select(x => new { + var result = globalSession.Query.All().Select(x => new { x.Id, String = x.FString, PadLeft = x.FString.PadLeft(10), @@ -255,6 +554,174 @@ public void PaddingTest() } } + [Test] + public void PaddingServerSideTest() + { + Require.ProviderIsNot(StorageProvider.Firebird); + + var checkForWhitespaceString = StorageProviderInfo.Instance.Provider.HasFlag(StorageProvider.Oracle) + ? (emptyStringIsNull && whitespaceStringAsEmptyString) || autoTrimWhiteSpaces + : emptyStringIsNull || autoTrimWhiteSpaces || whitespaceStringAsEmptyString; + + foreach (var value in testValues) { + var expectedValue = value == StringOfWhiteSpaces && checkForWhitespaceString + ? 2 : 1; + + var result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.PadLeft(10) == value.PadLeft(10)).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.PadLeft(10)"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.PadRight(10) == value.PadRight(10)).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.PadRight(10)"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.PadLeft(10, 'X') == value.PadLeft(10, 'X')).Count(); + Assert.That(result, Is.EqualTo(1), $"Failed for '{value}'.PadLeft(10, 'X')"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.PadRight(10, 'X') == value.PadRight(10, 'X')).Count(); + Assert.That(result, Is.EqualTo(1), $"Failed for '{value}'.PadRight(10, 'X')"); + } + } + + [Test] + public void PaddingServerSideFirebirdTest() + { + Require.ProviderIs(StorageProvider.Firebird, "Cuts-off length of result string if it is bigger than endlength in LPAD/RPAD"); + + foreach (var value in testValues.Where(s => s != StringOfWhiteSpaces)) { + var result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.PadLeft(10) == value.PadLeft(10).Substring(0, 10)).Count(); + Assert.That(result, Is.EqualTo(1), $"Failed for '{value}'.PadLeft(10)"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.PadRight(10) == value.PadRight(10).Substring(0, 10)).Count(); + Assert.That(result, Is.EqualTo(1), $"Failed for '{value}'.PadRight(10)"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.PadLeft(10, 'X') == value.PadLeft(10, 'X').Substring(0, 10)).Count(); + Assert.That(result, Is.EqualTo(1), $"Failed for '{value}'.PadLeft(10, 'X')"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.PadRight(10, 'X') == value.PadRight(10, 'X').Substring(0, 10)).Count(); + Assert.That(result, Is.EqualTo(1), $"Failed for '{value}'.PadRight(10, 'X')"); + } + + foreach (var value in testValues.Where(s => s == StringOfWhiteSpaces)) { + var result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.PadLeft(10) == string.Empty.PadLeft(10)).Count(); + Assert.That(result, Is.EqualTo(2), $"Failed for '{value}'.PadLeft(10)"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.PadRight(10) == string.Empty.PadRight(10)).Count(); + Assert.That(result, Is.EqualTo(2), $"Failed for '{value}'.PadRight(10)"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.PadLeft(10, 'X') == string.Empty.PadLeft(10, 'X')).Count(); + Assert.That(result, Is.EqualTo(1), $"Failed for '{value}'.PadLeft(10, 'X')"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.PadRight(10, 'X') == string.Empty.PadRight(10, 'X')).Count(); + Assert.That(result, Is.EqualTo(1), $"Failed for '{value}'.PadRight(10, 'X')"); + } + } + + #endregion + + #region IndexOf + + [Test] + public void IndexOfTest() + { + var comparison = StorageProviderInfo.Instance.Provider switch { + StorageProvider.MySql => StringComparison.InvariantCultureIgnoreCase, + StorageProvider.SqlServer => StringComparison.InvariantCultureIgnoreCase, + _ => StringComparison.InvariantCulture + }; + + var _char = 'o'; + var baseQuery = globalSession.Query.All().Where(x => x.FString != string.Empty); + if (emptyStringIsNull || autoTrimWhiteSpaces) { + baseQuery = baseQuery.Where(x => x.FString != null); + } + + var results = baseQuery + .Select(c => new { + String = c.FString, + IndexOfChar = c.FString.IndexOf(_char), + IndexOfCharStart = c.FString.IndexOf(_char, 1), + IndexOfCharStartCount = c.FString.IndexOf(_char, 1, 1), + IndexOfString = c.FString.IndexOf(_char.ToString()), + IndexOfStringStart = c.FString.IndexOf(_char.ToString(), 1), + IndexOfStringStartCount = c.FString.IndexOf(_char.ToString(), 1, 1) + }) + .ToList(); + foreach (var x in results) { + Assert.AreEqual(ConvertString(x.String).IndexOf(_char), x.IndexOfChar); + Assert.AreEqual(ConvertString(x.String).IndexOf(_char, 1), x.IndexOfCharStart); + Assert.AreEqual(ConvertString(x.String).IndexOf(_char, 1, 1), x.IndexOfCharStartCount); + Assert.AreEqual(ConvertString(x.String).IndexOf(_char.ToString()), x.IndexOfString); + Assert.AreEqual(ConvertString(x.String).IndexOf(_char.ToString(), 1), x.IndexOfStringStart); + Assert.AreEqual(ConvertString(x.String).IndexOf(_char.ToString(), 1, 1), x.IndexOfStringStartCount); + } + } + + [Test] + public void IndexOfServerSideTest() + { + Require.ProviderIsNot(StorageProvider.Sqlite, "No support for Position operation."); + + var comparison = StorageProviderInfo.Instance.Provider switch { + StorageProvider.MySql => StringComparison.InvariantCultureIgnoreCase, + StorageProvider.SqlServer => StringComparison.InvariantCultureIgnoreCase, + _ => StringComparison.InvariantCulture + }; + + var _char = 'o'; + foreach (var value in testValues) { + var expectedValue = value == StringOfWhiteSpaces && whitespaceStringAsEmptyString + ? 2 : 1; + + var result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.IndexOf(_char) == value.IndexOf(_char.ToString(), comparison)).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.IndexOf(_char)"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.IndexOf(_char, 1) == value.IndexOf(_char.ToString(), 1, comparison)).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.IndexOf(_char, 1)"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.IndexOf(_char, 1, 1) == value.IndexOf(_char.ToString(), 1, 1, comparison)).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.TrimStart('0', '1')"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.IndexOf(_char.ToString()) == value.IndexOf(_char.ToString(), comparison)).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.IndexOf(_char.ToString())"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.IndexOf(_char.ToString(), 1) == value.IndexOf(_char.ToString(), 1, comparison)).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.IndexOf(_char.ToString(), 1)"); + + result = globalSession.Query.All() + .Where(x => x.FString == value && x.FString.IndexOf(_char.ToString(), 1, 1) == value.IndexOf(_char.ToString(), 1, 1, comparison)).Count(); + Assert.That(result, Is.EqualTo(expectedValue), $"Failed for '{value}'.IndexOf(_char.ToString(), 1, 1)"); + } + } + + [Test] + public void IndexOfSqliteServerSideTest() + { + Require.ProviderIs(StorageProvider.Sqlite); + + var _char = 'o'; + var exception = Assert.Throws(() => + globalSession.Query.All() + .Where(x => x.FString.IndexOf(_char) > 0) + .ToList()); + Assert.That(exception.InnerException, Is.InstanceOf()); + } + #endregion private string ConvertString(string value) => From b4022def4e2610332b05331b028213fdcc4a67b4 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Fri, 1 Nov 2024 16:03:06 +0500 Subject: [PATCH 2/8] Improve string.Trim/TrimStart/TrimEnd support --- .../MemberCompilers/StringCompilers.cs | 58 ++++++++++++++++--- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/StringCompilers.cs b/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/StringCompilers.cs index 9fd23702ab..210c00de27 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/StringCompilers.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/Expressions/MemberCompilers/StringCompilers.cs @@ -139,17 +139,38 @@ public static SqlExpression StringTrim(SqlExpression _this) return SqlDml.Trim(_this); } + [Compiler(typeof(string), nameof(string.TrimStart))] + public static SqlExpression StringTrimStart(SqlExpression _this) + { + return SqlDml.Trim(_this, SqlTrimType.Leading); + } + + [Compiler(typeof(string), nameof(string.TrimEnd))] + public static SqlExpression StringTrimEnd(SqlExpression _this) + { + return SqlDml.Trim(_this, SqlTrimType.Trailing); + } + private static SqlExpression GenericTrim(SqlExpression _this, SqlExpression trimChars, SqlTrimType trimType) { - if (trimChars is SqlNull) + if (trimChars is SqlNull) { return SqlDml.Trim(_this, trimType); - if (!(trimChars is SqlContainer container)) - throw new NotSupportedException(Strings.ExStringTrimSupportedOnlyWithConstants); - if (!(container.Value is char[] chars)) - throw new NotSupportedException(Strings.ExStringTrimSupportedOnlyWithConstants); - return chars.Length==0 - ? SqlDml.Trim(_this, trimType) - : SqlDml.Trim(_this, trimType, new string(chars)); + } + if (trimChars is SqlLiteral oneChar) { + return SqlDml.Trim(_this, trimType, oneChar.Value.ToString()); + } + if (trimChars is SqlContainer container && container.Value is char[] chars) { + if (chars.Length == 0) { + return SqlDml.Trim(_this, trimType); + } + + var context = ExpressionTranslationContext.Current; + var provider = context.ProviderInfo.ProviderName; + return provider.Equals(WellKnown.Provider.Firebird, StringComparison.Ordinal) + ? chars.Aggregate(_this, (current, @char) => SqlDml.Trim(current, trimType, @char.ToString())) + : SqlDml.Trim(_this, trimType, new string(chars)); + } + throw new NotSupportedException(Strings.ExStringTrimSupportedOnlyWithConstants); } [Compiler(typeof(string), nameof(string.Trim))] @@ -159,6 +180,13 @@ public static SqlExpression StringTrim(SqlExpression _this, return GenericTrim(_this, trimChars, SqlTrimType.Both); } + [Compiler(typeof(string), nameof(string.Trim))] + public static SqlExpression StringTrimOneChar(SqlExpression _this, + [Type(typeof(char))] SqlExpression trimChar) + { + return GenericTrim(_this, trimChar, SqlTrimType.Both); + } + [Compiler(typeof(string), nameof(string.TrimStart))] public static SqlExpression StringTrimStart(SqlExpression _this, [Type(typeof(char[]))] SqlExpression trimChars) @@ -166,6 +194,13 @@ public static SqlExpression StringTrimStart(SqlExpression _this, return GenericTrim(_this, trimChars, SqlTrimType.Leading); } + [Compiler(typeof(string), nameof(string.TrimStart))] + public static SqlExpression StringTrimStartOneChar(SqlExpression _this, + [Type(typeof(char))] SqlExpression trimChar) + { + return GenericTrim(_this, trimChar, SqlTrimType.Leading); + } + [Compiler(typeof(string), nameof(string.TrimEnd))] public static SqlExpression StringTrimEnd(SqlExpression _this, [Type(typeof(char[]))] SqlExpression trimChars) @@ -173,6 +208,13 @@ public static SqlExpression StringTrimEnd(SqlExpression _this, return GenericTrim(_this, trimChars, SqlTrimType.Trailing); } + [Compiler(typeof(string), nameof(string.TrimEnd))] + public static SqlExpression StringTrimEndOneChar(SqlExpression _this, + [Type(typeof(char))] SqlExpression trimChar) + { + return GenericTrim(_this, trimChar, SqlTrimType.Trailing); + } + [Compiler(typeof(string), nameof(string.Length), TargetKind.PropertyGet)] public static SqlExpression StringLength(SqlExpression _this) { From bf44a10dbf08b76c1bb2e33e0c09a7af031fef67 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Thu, 26 Dec 2024 19:44:02 +0500 Subject: [PATCH 3/8] MySQL: Fix wrong translation of NullIf --- Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Translator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Translator.cs b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Translator.cs index 8675b2e563..39b6012e17 100644 --- a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Translator.cs +++ b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Translator.cs @@ -101,7 +101,7 @@ public override void Translate(IOutput output, SqlFunctionType type) case SqlFunctionType.CurrentUser: _ = output.Append("CURRENT_USER()"); break; case SqlFunctionType.SessionUser: _ = output.Append("SESSION_USER()"); break; - case SqlFunctionType.NullIf: _ = output.Append("IFNULL"); break; + //case SqlFunctionType.NullIf: _ = output.Append("IFNULL"); break; //datetime/timespan case SqlFunctionType.DateTimeTruncate: _ = output.Append("DATE"); break; case SqlFunctionType.CurrentDate: _ = output.Append("CURDATE()"); break; From 0c0b1d1a24a0d2fe447b8c980dc2198f25702ae9 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Fri, 27 Dec 2024 17:18:06 +0500 Subject: [PATCH 4/8] MySQL: Fix Pad operations LPAD/RPAD functions of MySQL requre actual character, so explicitly define whitespace as character. --- .../Sql.Drivers.MySql/v5_0/Compiler.cs | 2 +- Orm/Xtensive.Orm/Sql/SqlHelper.cs | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Compiler.cs b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Compiler.cs index 0a66284582..67413a471a 100644 --- a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Compiler.cs +++ b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Compiler.cs @@ -160,7 +160,7 @@ public override void Visit(SqlFunctionCall node) return; case SqlFunctionType.PadLeft: case SqlFunctionType.PadRight: - SqlHelper.GenericPad(node).AcceptVisitor(this); + SqlHelper.GenericPad(node, true).AcceptVisitor(this); return; case SqlFunctionType.Rand: SqlDml.FunctionCall(translator.TranslateToString(SqlFunctionType.Rand)).AcceptVisitor(this); diff --git a/Orm/Xtensive.Orm/Sql/SqlHelper.cs b/Orm/Xtensive.Orm/Sql/SqlHelper.cs index c9e4a8151c..ebb6f764de 100644 --- a/Orm/Xtensive.Orm/Sql/SqlHelper.cs +++ b/Orm/Xtensive.Orm/Sql/SqlHelper.cs @@ -331,7 +331,7 @@ public static string TimeSpanToString(TimeSpan value, string format) days, hours, minutes, seconds, milliseconds); } - public static SqlExpression GenericPad(SqlFunctionCall node) + public static SqlExpression GenericPad(SqlFunctionCall node, bool padStringRequired = false) { string paddingFunction; switch (node.FunctionType) { @@ -346,9 +346,12 @@ public static SqlExpression GenericPad(SqlFunctionCall node) } var operand = node.Arguments[0]; var result = SqlDml.Case(); - result.Add( - SqlDml.CharLength(operand) < node.Arguments[1], - SqlDml.FunctionCall(paddingFunction, node.Arguments)); + var lenghtCheck = SqlDml.CharLength(operand) < node.Arguments[1]; + var paddingItself = (padStringRequired && node.Arguments.Count < 3) + ? SqlDml.FunctionCall(paddingFunction, operand, node.Arguments[1], SqlDml.Literal(" ")) + : SqlDml.FunctionCall(paddingFunction, node.Arguments); + + _ = result.Add(lenghtCheck, paddingItself); result.Else = operand; return result; } From 36062d4b3aaa3641d18b26eca34bbd5f089079c7 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Sat, 28 Dec 2024 17:41:19 +0500 Subject: [PATCH 5/8] SQLite: Fix string.Length translation and implement PadLeft/PadRight operations --- .../Sql.Drivers.Sqlite/v3/Compiler.cs | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Compiler.cs b/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Compiler.cs index dc044373f7..14580f4c92 100644 --- a/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Compiler.cs +++ b/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Compiler.cs @@ -151,10 +151,11 @@ public override void Visit(SqlFunctionCall node) var arguments = node.Arguments; switch (node.FunctionType) { case SqlFunctionType.CharLength: - (SqlDml.FunctionCall("LENGTH", arguments) / 2).AcceptVisitor(this); + SqlDml.FunctionCall("LENGTH", arguments).AcceptVisitor(this); return; case SqlFunctionType.PadLeft: case SqlFunctionType.PadRight: + Visit(EmulateLpadOrRpad(arguments, node.FunctionType is SqlFunctionType.PadLeft)); return; case SqlFunctionType.Concat: var nod = arguments[0]; @@ -631,6 +632,42 @@ private static SqlDateTimePart ConvertDateTimeOffsetPartToDateTimePart(SqlDateTi }; } + private static SqlCase EmulateLpadOrRpad(IReadOnlyList arguments, bool isLpad) + { + var operand = arguments[0]; + var charcount = arguments[1]; + if (charcount is not SqlLiteral intWidth) { + // Since we emulate function with contatination, we need to know total width + // to calculate prefix + throw SqlHelper.NotSupported("PadLeft/PadRight with expressions as total width."); + } + var totalWidth = intWidth.Value; + + var padChar = arguments switch { + _ when arguments.Count == 3 && arguments[2] is SqlLiteral charLiteral => charLiteral.Value, + _ when arguments.Count == 2 => ' ', + _ => throw new NotSupportedException() + }; + + var paddingString = SqlDml.Literal(new string(Enumerable.Repeat(padChar, intWidth.Value).ToArray())); + + var padExpression = isLpad + ? SqlDml.Substring( + SqlDml.Concat(paddingString, operand), + SqlDml.Literal(-totalWidth - 1),// handles '+1' operation in translation of substring function call + SqlDml.Literal(totalWidth)) + : SqlDml.Substring( + SqlDml.Concat(operand, paddingString), + SqlDml.Literal(0), // handles '+1' operation in translation of substring function call + SqlDml.Literal(totalWidth)); + + var @case = SqlDml.Case(); + _ = @case.Add(SqlDml.CharLength(operand) >= charcount, operand); + @case.Else = padExpression; + return @case; + } + + // Constructors /// The driver. From d487fb60946b300c892d16f8012e91a72b2edfa5 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Wed, 22 Jan 2025 12:55:13 +0500 Subject: [PATCH 6/8] Remove commented code --- Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Translator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Translator.cs b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Translator.cs index 39b6012e17..bba697a80b 100644 --- a/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Translator.cs +++ b/Orm/Xtensive.Orm.MySql/Sql.Drivers.MySql/v5_0/Translator.cs @@ -101,7 +101,6 @@ public override void Translate(IOutput output, SqlFunctionType type) case SqlFunctionType.CurrentUser: _ = output.Append("CURRENT_USER()"); break; case SqlFunctionType.SessionUser: _ = output.Append("SESSION_USER()"); break; - //case SqlFunctionType.NullIf: _ = output.Append("IFNULL"); break; //datetime/timespan case SqlFunctionType.DateTimeTruncate: _ = output.Append("DATE"); break; case SqlFunctionType.CurrentDate: _ = output.Append("CURDATE()"); break; From e77d2eccf3f492bdcfdee19ece2a0194e78213ef Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Wed, 22 Jan 2025 13:03:53 +0500 Subject: [PATCH 7/8] Rename function --- Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Compiler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Compiler.cs b/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Compiler.cs index 14580f4c92..b7b5823f8e 100644 --- a/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Compiler.cs +++ b/Orm/Xtensive.Orm.Sqlite/Sql.Drivers.Sqlite/v3/Compiler.cs @@ -155,7 +155,7 @@ public override void Visit(SqlFunctionCall node) return; case SqlFunctionType.PadLeft: case SqlFunctionType.PadRight: - Visit(EmulateLpadOrRpad(arguments, node.FunctionType is SqlFunctionType.PadLeft)); + Visit(EmulateLpadRpad(arguments, node.FunctionType is SqlFunctionType.PadLeft)); return; case SqlFunctionType.Concat: var nod = arguments[0]; @@ -632,7 +632,7 @@ private static SqlDateTimePart ConvertDateTimeOffsetPartToDateTimePart(SqlDateTi }; } - private static SqlCase EmulateLpadOrRpad(IReadOnlyList arguments, bool isLpad) + private static SqlCase EmulateLpadRpad(IReadOnlyList arguments, bool isLpad) { var operand = arguments[0]; var charcount = arguments[1]; From e281961a593fe98d080644ef0871546caa2d8419 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Wed, 22 Jan 2025 13:04:41 +0500 Subject: [PATCH 8/8] Improve changelog --- ChangeLog/7.2.0-Beta-2-dev.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ChangeLog/7.2.0-Beta-2-dev.txt b/ChangeLog/7.2.0-Beta-2-dev.txt index 0e7ff10a24..29d4b11bcb 100644 --- a/ChangeLog/7.2.0-Beta-2-dev.txt +++ b/ChangeLog/7.2.0-Beta-2-dev.txt @@ -1 +1,6 @@ -[main] Upgrade hints change names of constructors' string parameters for better understanding of what suppose to be in them. \ No newline at end of file +[main] Upgrade hints change names of constructors' string parameters for better understanding of what suppose to be in them. +[main] Improved string operations Trim/TrimStart/TrimEnd support +[mysql] SqlDml.NullIf function now correctly translated +[mysql] Improved support for string.PadLeft/PadRight opertaions +[sqlite] Fixed string.Lenght translation +[sqlite] Added support for string.PadLeft/PadRight operations \ No newline at end of file