IGNITE-4723 .NET: Support REGEXP_LIKE in LINQ
authorAlexey Popov <tank2.alex@gmail.com>
Fri, 13 Oct 2017 11:19:14 +0000 (14:19 +0300)
committerPavel Tupitsyn <ptupitsyn@apache.org>
Fri, 13 Oct 2017 11:19:14 +0000 (14:19 +0300)
This closes #2842

modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Strings.cs
modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs
modules/platforms/dotnet/Apache.Ignite.Linq/Impl/MethodVisitor.cs

index 1139c4d..35996b0 100644 (file)
@@ -83,6 +83,24 @@ namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
             Assert.Throws<NotSupportedException>(() => CheckFunc(x => x.TrimEnd(toTrimFails), strings));
 
             CheckFunc(x => Regex.Replace(x, @"son.\d", "kele!"), strings);
+            CheckFunc(x => Regex.Replace(x, @"son.\d", "kele!", RegexOptions.None), strings);
+            CheckFunc(x => Regex.Replace(x, @"person.\d", "akele!", RegexOptions.IgnoreCase), strings);
+            CheckFunc(x => Regex.Replace(x, @"person.\d", "akele!", RegexOptions.Multiline), strings);
+            CheckFunc(x => Regex.Replace(x, @"person.\d", "akele!", RegexOptions.IgnoreCase | RegexOptions.Multiline), 
+                strings);
+            var notSupportedException = Assert.Throws<NotSupportedException>(() => CheckFunc(x => 
+                Regex.IsMatch(x, @"^person\d", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant), strings));
+            Assert.AreEqual("RegexOptions.CultureInvariant is not supported", notSupportedException.Message);
+
+            CheckFunc(x => Regex.IsMatch(x, @"^Person_9\d"), strings);
+            CheckFunc(x => Regex.IsMatch(x, @"^person_9\d", RegexOptions.None), strings);
+            CheckFunc(x => Regex.IsMatch(x, @"^person_9\d", RegexOptions.IgnoreCase), strings);
+            CheckFunc(x => Regex.IsMatch(x, @"^Person_9\d", RegexOptions.Multiline), strings);
+            CheckFunc(x => Regex.IsMatch(x, @"^person_9\d", RegexOptions.IgnoreCase | RegexOptions.Multiline), strings);
+            notSupportedException = Assert.Throws<NotSupportedException>(() => CheckFunc(x => 
+                Regex.IsMatch(x, @"^person_9\d",RegexOptions.IgnoreCase | RegexOptions.CultureInvariant), strings));
+            Assert.AreEqual("RegexOptions.CultureInvariant is not supported", notSupportedException.Message);
+
             CheckFunc(x => x.Replace("son", ""), strings);
             CheckFunc(x => x.Replace("son", "kele"), strings);
 
index d187f08..4caefe1 100644 (file)
@@ -474,6 +474,11 @@ namespace Apache.Ignite.Linq.Impl
         [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")]
         protected override Expression VisitConstant(ConstantExpression expression)
         {
+            if (MethodVisitor.VisitConstantCall(expression, this))
+            {
+                return expression;
+            }
+
             AppendParameter(expression.Value);
 
             return expression;
index 8abf2a6..84bd98f 100644 (file)
@@ -71,8 +71,12 @@ namespace Apache.Ignite.Linq.Impl
             GetStringMethod("PadRight", "rpad", typeof (int)),
             GetStringMethod("PadRight", "rpad", typeof (int), typeof (char)),
 
-            GetMethod(typeof (Regex), "Replace", new[] {typeof (string), typeof (string), typeof (string)}, 
-                GetFunc("regexp_replace")),
+            GetRegexMethod("Replace", "regexp_replace", typeof (string), typeof (string), typeof (string)),
+            GetRegexMethod("Replace", "regexp_replace", typeof (string), typeof (string), typeof (string), 
+                typeof(RegexOptions)),
+            GetRegexMethod("IsMatch", "regexp_like", typeof (string), typeof (string)),
+            GetRegexMethod("IsMatch", "regexp_like", typeof (string), typeof (string), typeof(RegexOptions)),
+
             GetMethod(typeof (DateTime), "ToString", new[] {typeof (string)},
                 (e, v) => VisitFunc(e, v, "formatdatetime", ", 'en', 'UTC'")),
 
@@ -117,6 +121,13 @@ namespace Apache.Ignite.Linq.Impl
             GetMathMethod("Truncate", typeof (decimal)),
         }.ToDictionary(x => x.Key, x => x.Value);
 
+        /// <summary> RegexOptions transformations. </summary>
+        private static readonly Dictionary<RegexOptions, string> RegexOptionFlags = new Dictionary<RegexOptions, string>
+        {
+            { RegexOptions.IgnoreCase, "i" },
+            { RegexOptions.Multiline, "m" }
+        };
+
         /// <summary>
         /// Visits the property call expression.
         /// </summary>
@@ -153,6 +164,37 @@ namespace Apache.Ignite.Linq.Impl
         }
 
         /// <summary>
+        /// Visits the constant call expression.
+        /// </summary>
+        public static bool VisitConstantCall(ConstantExpression expression, CacheQueryExpressionVisitor visitor)
+        {
+            if (expression.Type != typeof(RegexOptions))
+            {
+                return false;
+            }
+
+            var regexOptions = expression.Value as RegexOptions? ?? RegexOptions.None;
+            var result = string.Empty;
+            foreach (var option in RegexOptionFlags)
+            {
+                if (regexOptions.HasFlag(option.Key))
+                {
+                    result += option.Value;
+                    regexOptions &= ~option.Key;
+                }
+            }
+
+            if (regexOptions != RegexOptions.None)
+            {
+                throw new NotSupportedException(string.Format("RegexOptions.{0} is not supported", regexOptions));
+            }
+
+            visitor.AppendParameter(result);
+
+            return true;
+        }
+
+        /// <summary>
         /// Gets the function.
         /// </summary>
         private static VisitMethodDelegate GetFunc(string func, params int[] adjust)
@@ -302,6 +344,15 @@ namespace Apache.Ignite.Linq.Impl
         }
 
         /// <summary>
+        /// Gets the Regex method.
+        /// </summary>
+        private static KeyValuePair<MethodInfo, VisitMethodDelegate> GetRegexMethod(string name, string sqlName,
+            params Type[] argTypes)
+        {
+            return GetMethod(typeof(Regex), name, argTypes, GetFunc(sqlName));
+        }
+
+        /// <summary>
         /// Gets string parameterized Trim(TrimStart, TrimEnd) method.
         /// </summary>
         private static KeyValuePair<MethodInfo, VisitMethodDelegate> GetParameterizedTrimMethod(string name,