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,