You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pt...@apache.org on 2021/04/16 08:25:54 UTC

[ignite] branch master updated: IGNITE-14523 .NET: Add string.Compare support to LINQ provider

This is an automated email from the ASF dual-hosted git repository.

ptupitsyn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 074046b  IGNITE-14523 .NET: Add string.Compare support to LINQ provider
074046b is described below

commit 074046b30422dda66e41c2e51d4455f61a986046
Author: Oleg Bevz <ol...@inbox.ru>
AuthorDate: Fri Apr 16 11:25:28 2021 +0300

    IGNITE-14523 .NET: Add string.Compare support to LINQ provider
    
    Add support for string.Compare(string, string) and string.Compare(string, string, bool).
---
 .../Cache/Query/Linq/CacheLinqTest.Base.cs         | 31 ++++++++++
 .../Cache/Query/Linq/CacheLinqTest.Strings.cs      | 71 ++++++++++++++++++++++
 .../Apache.Ignite.Linq/Impl/MethodVisitor.cs       | 54 ++++++++++++++++
 3 files changed, 156 insertions(+)

diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Base.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Base.cs
index c70b692..8bcf4c0 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Base.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Base.cs
@@ -271,6 +271,37 @@ namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
         /// <summary>
         /// Checks that function used in Where Clause maps to SQL function properly
         /// </summary>
+        private static void CheckWhereFunc<T>(Expression<Func<T, bool>> expression, IQueryable<T> query)
+        {
+            CheckWhereFunc(expression, expression, query);
+        }
+
+        /// <summary>
+        /// Checks that function used in Where Clause maps to SQL function properly
+        /// </summary>
+        private static void CheckWhereFunc<T>(Expression<Func<T, bool>> memoryExpression, 
+            Expression<Func<T, bool>> sqlExpression, IQueryable<T> query)
+        {
+            // Calculate result locally, using real method invocation
+            var expected = query.ToArray().AsQueryable().Where(memoryExpression).OrderBy(x => x).ToArray();
+
+            // Perform SQL query
+            var actual = query.Where(sqlExpression).ToArray().OrderBy(x => x).ToArray();
+
+            // Compare results
+            CollectionAssert.AreEqual(expected, actual, new NumericComparer());
+
+            // Perform intermediate anonymous type conversion to check type projection
+            actual = query.Where(sqlExpression).Select(x => new { Foo = x }).ToArray().Select(x => x.Foo)
+                .OrderBy(x => x).ToArray();
+
+            // Compare results
+            CollectionAssert.AreEqual(expected, actual, new NumericComparer());
+        }
+
+        /// <summary>
+        /// Checks that function used in Where Clause maps to SQL function properly
+        /// </summary>
         private static void CheckWhereFunc<TKey, TEntry>(IQueryable<ICacheEntry<TKey,TEntry>> query,
             Expression<Func<ICacheEntry<TKey, TEntry>,bool>> whereExpression)
         {
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Strings.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Strings.cs
index 0c0aac0..5a79f93 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Strings.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Strings.cs
@@ -28,6 +28,7 @@ namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
 {
     using System;
     using System.Collections.Generic;
+    using System.Diagnostics.CodeAnalysis;
     using System.Linq;
     using System.Text.RegularExpressions;
     using Apache.Ignite.Linq;
@@ -42,6 +43,8 @@ namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
         /// Tests strings.
         /// </summary>
         [Test]
+        [SuppressMessage("ReSharper", "StringCompareIsCultureSpecific.2", Justification = "SQL")]
+        [SuppressMessage("ReSharper", "StringCompareIsCultureSpecific.3", Justification = "SQL")]
         public void TestStrings()
         {
             var strings = GetSecondPersonCache().AsCacheQueryable().Select(x => x.Value.Name);
@@ -110,6 +113,74 @@ namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
 
             // String + int
             CheckFunc(x => x + 10, strings);
+
+            // string.Compare(string strA, string strB)
+            CheckWhereFunc(x => string.Compare(x, "Person_1300") < 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "Person_1300") > 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "Person_1300") == 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "Person_1300") <= 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "Person_1300") >= 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "person_1300", StringComparison.Ordinal) < 0, x => string.Compare(x, "person_1300") < 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "person_1300", StringComparison.Ordinal) > 0, x => string.Compare(x, "person_1300") > 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "person_1300") == 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "person_1300", StringComparison.Ordinal) <= 0, x => string.Compare(x, "person_1300") <= 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "person_1300", StringComparison.Ordinal) >= 0, x => string.Compare(x, "person_1300") >= 0, strings);
+            CheckWhereFunc(x => string.Compare(x, null) == 0, strings);
+            CheckWhereFunc(x => string.Compare(x, null) < 0, strings);
+            CheckWhereFunc(x => string.Compare(x, null) > 0, strings);
+            CheckWhereFunc(x => string.Compare(null, x) == 0, strings);
+            CheckWhereFunc(x => string.Compare(null, x) < 0, strings);
+            CheckWhereFunc(x => string.Compare(null, x) > 0, strings);
+            CheckWhereFunc(x => string.Compare(null, null) == 0, strings);
+            CheckWhereFunc(x => string.Compare(null, null) < 0, strings);
+            CheckWhereFunc(x => string.Compare(null, null) > 0, strings);
+
+            // string.Compare(string strA, string strB, true)
+            CheckWhereFunc(x => string.Compare(x, "Person_1300", true) < 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "Person_1300", true) > 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "Person_1300", true) == 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "Person_1300", true) <= 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "Person_1300", true) >= 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "person_1300", true) < 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "person_1300", true) > 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "person_1300", true) == 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "person_1300", true) <= 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "person_1300", true) >= 0, strings);
+            CheckWhereFunc(x => string.Compare(x, null, true) == 0, strings);
+            CheckWhereFunc(x => string.Compare(x, null, true) < 0, strings);
+            CheckWhereFunc(x => string.Compare(x, null, true) > 0, strings);
+            CheckWhereFunc(x => string.Compare(null, x, true) == 0, strings);
+            CheckWhereFunc(x => string.Compare(null, x, true) < 0, strings);
+            CheckWhereFunc(x => string.Compare(null, x, true) > 0, strings);
+            CheckWhereFunc(x => string.Compare(null, null, true) == 0, strings);
+            CheckWhereFunc(x => string.Compare(null, null, true) < 0, strings);
+            CheckWhereFunc(x => string.Compare(null, null, true) > 0, strings);
+
+            // string.Compare(string strA, string strB, false)
+            CheckWhereFunc(x => string.Compare(x, "Person_1300", false) < 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "Person_1300", false) > 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "Person_1300", false) == 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "Person_1300", false) <= 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "Person_1300", false) >= 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "person_1300", StringComparison.Ordinal) < 0, x => string.Compare(x, "person_1300", false) < 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "person_1300", StringComparison.Ordinal) > 0, x => string.Compare(x, "person_1300", false) > 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "person_1300", false) == 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "person_1300", StringComparison.Ordinal) <= 0, x => string.Compare(x, "person_1300", false) <= 0, strings);
+            CheckWhereFunc(x => string.Compare(x, "person_1300", StringComparison.Ordinal) >= 0, x => string.Compare(x, "person_1300", false) >= 0, strings);
+            CheckWhereFunc(x => string.Compare(x, null, false) == 0, strings);
+            CheckWhereFunc(x => string.Compare(x, null, false) < 0, strings);
+            CheckWhereFunc(x => string.Compare(x, null, false) > 0, strings);
+            CheckWhereFunc(x => string.Compare(null, x, false) == 0, strings);
+            CheckWhereFunc(x => string.Compare(null, x, false) < 0, strings);
+            CheckWhereFunc(x => string.Compare(null, x, false) > 0, strings);
+            CheckWhereFunc(x => string.Compare(null, null, false) == 0, strings);
+            CheckWhereFunc(x => string.Compare(null, null, false) < 0, strings);
+            CheckWhereFunc(x => string.Compare(null, null, false) > 0, strings);
+
+            // ReSharper disable once ReturnValueOfPureMethodIsNotUsed (force LINQ evaluation)
+            Assert.Throws<NotSupportedException>(
+                () => strings.Where(x => string.Compare(x, "person_1300", x.StartsWith("Person")) == 0).ToArray(),
+                "Parameter 'ignoreCase' from 'string.Compare' method should be specified as a constant expression");
         }
     }
 }
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/MethodVisitor.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/MethodVisitor.cs
index 5771f88..7e3d475 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/MethodVisitor.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/MethodVisitor.cs
@@ -77,6 +77,8 @@ namespace Apache.Ignite.Linq.Impl
             GetStringMethod("PadLeft", "lpad", typeof (int), typeof (char)),
             GetStringMethod("PadRight", "rpad", typeof (int)),
             GetStringMethod("PadRight", "rpad", typeof (int), typeof (char)),
+            GetStringMethod("Compare", new[] { typeof(string), typeof(string) }, (e, v) => VisitStringCompare(e, v, false)),
+            GetStringMethod("Compare", new[] { typeof(string), typeof(string), typeof(bool) }, (e, v) => VisitStringCompare(e, v, GetStringCompareIgnoreCaseParameter(e.Arguments[2]))),
 
             GetRegexMethod("Replace", "regexp_replace", typeof (string), typeof (string), typeof (string)),
             GetRegexMethod("Replace", "regexp_replace", typeof (string), typeof (string), typeof (string),
@@ -330,6 +332,58 @@ namespace Apache.Ignite.Linq.Impl
         }
 
         /// <summary>
+        /// Get IgnoreCase parameter for string.Compare method
+        /// </summary>
+        private static bool GetStringCompareIgnoreCaseParameter(Expression expression)
+        {
+            var constant = expression as ConstantExpression;
+            if (constant != null)
+            {
+                if (constant.Value is bool)
+                {
+                    return (bool)constant.Value;
+                }
+            }
+
+            throw new NotSupportedException(
+                "Parameter 'ignoreCase' from 'string.Compare method should be specified as a constant expression");
+        }
+
+        /// <summary>
+        /// Visits string.Compare method
+        /// </summary>
+        private static void VisitStringCompare(MethodCallExpression expression, CacheQueryExpressionVisitor visitor, bool ignoreCase)
+        {
+            // Ex: nvl2(?, casewhen(_T0.NAME = ?, 0, casewhen(_T0.NAME >= ?, 1, -1)), 1)
+            visitor.ResultBuilder.Append("nvl2(");
+            visitor.Visit(expression.Arguments[1]);
+            visitor.ResultBuilder.Append(", casewhen(");
+            VisitArg(visitor, expression, 0, ignoreCase);
+            visitor.ResultBuilder.Append(" = ");
+            VisitArg(visitor, expression, 1, ignoreCase);
+            visitor.ResultBuilder.Append(", 0, casewhen(");
+            VisitArg(visitor, expression, 0, ignoreCase);
+            visitor.ResultBuilder.Append(" >= ");
+            VisitArg(visitor, expression, 1, ignoreCase);
+            visitor.ResultBuilder.Append(", 1, -1)), 1)");
+        }
+
+        /// <summary>
+        /// Visits member expression argument.
+        /// </summary>
+        private static void VisitArg(CacheQueryExpressionVisitor visitor, MethodCallExpression expression, int idx,
+            bool lower)
+        {
+            if (lower)
+                visitor.ResultBuilder.Append("lower(");
+
+            visitor.Visit(expression.Arguments[idx]);
+
+            if (lower)
+                visitor.ResultBuilder.Append(")");
+        }
+
+        /// <summary>
         /// Gets the method.
         /// </summary>
         private static KeyValuePair<MethodInfo, VisitMethodDelegate> GetMethod(Type type, string name,