Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id ABBB9200C61 for ; Tue, 11 Apr 2017 03:38:04 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id AA760160BC0; Tue, 11 Apr 2017 01:38:04 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 412FD160BB6 for ; Tue, 11 Apr 2017 03:38:03 +0200 (CEST) Received: (qmail 2572 invoked by uid 500); 11 Apr 2017 01:38:02 -0000 Mailing-List: contact commits-help@ignite.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@ignite.apache.org Delivered-To: mailing list commits@ignite.apache.org Received: (qmail 2135 invoked by uid 99); 11 Apr 2017 01:38:01 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 11 Apr 2017 01:38:01 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id B7812F170C; Tue, 11 Apr 2017 01:38:01 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: dmagda@apache.org To: commits@ignite.apache.org Date: Tue, 11 Apr 2017 01:38:23 -0000 Message-Id: <6c1c43e35fe94968b06d69238f447fb8@git.apache.org> In-Reply-To: <4a19f1c5824c45d59379118d4b0ab2de@git.apache.org> References: <4a19f1c5824c45d59379118d4b0ab2de@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [23/50] [abbrv] ignite git commit: IGNITE-4817 .NET: Fixed LINQ Contains when subquery comes from a variable archived-at: Tue, 11 Apr 2017 01:38:04 -0000 IGNITE-4817 .NET: Fixed LINQ Contains when subquery comes from a variable This closes #1745 Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/7c80c477 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/7c80c477 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/7c80c477 Branch: refs/heads/ignite-1192 Commit: 7c80c477d38c59cd64045c5eb9f048d367a7978d Parents: 21d3475 Author: Sergey Stronchinskiy Authored: Thu Apr 6 10:31:19 2017 +0300 Committer: Pavel Tupitsyn Committed: Thu Apr 6 10:31:19 2017 +0300 ---------------------------------------------------------------------- .../Cache/Query/CacheLinqTest.cs | 36 ++++++++-- .../Impl/CacheQueryExpressionVisitor.cs | 14 +++- .../Apache.Ignite.Linq/Impl/CacheQueryParser.cs | 25 ++++++- .../Apache.Ignite.Linq/Impl/MethodVisitor.cs | 72 ++++++++++++++++---- 4 files changed, 125 insertions(+), 22 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/7c80c477/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheLinqTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheLinqTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheLinqTest.cs index 856fb49..dfd644d 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheLinqTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheLinqTest.cs @@ -276,12 +276,21 @@ namespace Apache.Ignite.Core.Tests.Cache.Query CheckFunc(x => x.Trim(), strings); CheckFunc(x => x.Trim('P'), strings); + var toTrim = new[] {'P'}; + CheckFunc(x => x.Trim(toTrim), strings); + CheckFunc(x => x.Trim(new List {'P'}.ToArray()), strings); CheckFunc(x => x.Trim('3'), strings); CheckFunc(x => x.TrimStart('P'), strings); + CheckFunc(x => x.TrimStart(toTrim), strings); CheckFunc(x => x.TrimStart('3'), strings); Assert.Throws(() => CheckFunc(x => x.TrimStart('P', 'e'), strings)); CheckFunc(x => x.TrimEnd('P'), strings); + CheckFunc(x => x.TrimEnd(toTrim), strings); CheckFunc(x => x.TrimEnd('3'), strings); + var toTrimFails = new[] {'P', 'c'}; + Assert.Throws(() => CheckFunc(x => x.Trim(toTrimFails), strings)); + Assert.Throws(() => CheckFunc(x => x.TrimStart(toTrimFails), strings)); + Assert.Throws(() => CheckFunc(x => x.TrimEnd(toTrimFails), strings)); CheckFunc(x => Regex.Replace(x, @"son.\d", "kele!"), strings); CheckFunc(x => x.Replace("son", ""), strings); @@ -726,7 +735,8 @@ namespace Apache.Ignite.Core.Tests.Cache.Query .ThenBy(x => x.Value.Age) .ToArray(); - Assert.AreEqual(Enumerable.Range(0, PersonCount).Reverse().ToArray(), persons.Select(x => x.Key).ToArray()); + Assert.AreEqual(Enumerable.Range(0, PersonCount).Reverse().ToArray(), + persons.Select(x => x.Key).ToArray()); var personsByOrg = GetPersonCache().AsCacheQueryable() .Join(GetOrgCache().AsCacheQueryable(), p => p.Value.OrganizationId, o => o.Value.Id, @@ -816,6 +826,22 @@ namespace Apache.Ignite.Core.Tests.Cache.Query var ex = Assert.Throws(() => CompiledQuery.Compile((int[] k) => cache.Where(x => k.Contains(x.Key)))); Assert.AreEqual("'Contains' clause coming from compiled query parameter is not supported.", ex.Message); + + // check subquery from another cache put in separate variable + var orgIds = orgCache + .Where(o => o.Value.Name == "Org_1") + .Select(o => o.Key); + + var subQueryFromVar = cache + .Where(x => orgIds.Contains(x.Value.OrganizationId)) + .ToArray(); + + var subQueryInline = cache + .Where(x => orgCache.Where(o => o.Value.Name == "Org_1") + .Select(o => o.Key).Contains(x.Value.OrganizationId)) + .ToArray(); + + Assert.AreEqual(subQueryInline.Length, subQueryFromVar.Length); } /// @@ -1047,7 +1073,7 @@ namespace Apache.Ignite.Core.Tests.Cache.Query var qry6 = CompiledQuery.Compile((int minAge) => persons .Select(x => x.Value) .Where(x => x.Age >= minAge) - .Select(x => new {x.Name, x.Age}) + .Select(x => new { x.Name, x.Age }) .OrderBy(x => x.Name)); var res = qry6(PersonCount - 3).GetAll(); @@ -1116,7 +1142,8 @@ namespace Apache.Ignite.Core.Tests.Cache.Query // 8 arg var qry8 = CompiledQuery.Compile((int a, int b, int c, int d, int e, int f, int g, int h) => - cache.Select(x => x.Key).Where(k => k > a && k > b && k > c && k < d && k < e && k < f && k < g && k < h)); + cache.Select(x => x.Key) + .Where(k => k > a && k > b && k > c && k < d && k < e && k < f && k < g && k < h)); Assert.AreEqual(new[] {3, 4}, qry8(0, 1, 2, 5, 6, 7, 8, 9).ToArray()); } @@ -1432,7 +1459,8 @@ namespace Apache.Ignite.Core.Tests.Cache.Query /// /// Checks that function used in Where Clause maps to SQL function properly /// - private static void CheckWhereFunc(IQueryable> query, Expression,bool>> whereExpression) + private static void CheckWhereFunc(IQueryable> query, + Expression,bool>> whereExpression) { // Calculate result locally, using real method invocation var expected = query http://git-wip-us.apache.org/repos/asf/ignite/blob/7c80c477/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs index 94e59fa..99712e3 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs @@ -93,26 +93,34 @@ namespace Apache.Ignite.Linq.Impl [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")] protected override Expression VisitUnary(UnaryExpression expression) { - ResultBuilder.Append("("); + var closeBracket = false; switch (expression.NodeType) { case ExpressionType.Negate: + ResultBuilder.Append("("); ResultBuilder.Append("-"); + closeBracket = true; break; + case ExpressionType.Not: + ResultBuilder.Append("("); ResultBuilder.Append("not "); + closeBracket = true; break; + case ExpressionType.Convert: // Ignore, let the db do the conversion break; + default: return base.VisitUnary(expression); } Visit(expression.Operand); - ResultBuilder.Append(")"); + if(closeBracket) + ResultBuilder.Append(")"); return expression; } @@ -418,7 +426,7 @@ namespace Apache.Ignite.Linq.Impl /// /// Appends the parameter. /// - private void AppendParameter(object value) + public void AppendParameter(object value) { ResultBuilder.Append("?"); http://git-wip-us.apache.org/repos/asf/ignite/blob/7c80c477/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryParser.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryParser.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryParser.cs index cee90f4..794ef2e 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryParser.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryParser.cs @@ -19,6 +19,7 @@ namespace Apache.Ignite.Linq.Impl { using System.Threading; using Remotion.Linq.Parsing.ExpressionVisitors.Transformation; + using Remotion.Linq.Parsing.ExpressionVisitors.TreeEvaluation; using Remotion.Linq.Parsing.Structure; using Remotion.Linq.Parsing.Structure.ExpressionTreeProcessors; @@ -46,11 +47,33 @@ namespace Apache.Ignite.Linq.Impl { var transformerRegistry = ExpressionTransformerRegistry.CreateDefault(); - var proc = new TransformingExpressionTreeProcessor(transformerRegistry); + var proc = CreateCompoundProcessor(transformerRegistry); var parser = new ExpressionTreeParser(ExpressionTreeParser.CreateDefaultNodeTypeProvider(), proc); return new QueryParser(parser); } + + /// + /// Creates CompoundExpressionTreeProcessor. + /// + private static CompoundExpressionTreeProcessor CreateCompoundProcessor( + IExpressionTranformationProvider tranformationProvider) + { + return new CompoundExpressionTreeProcessor( + new IExpressionTreeProcessor[] + { + new PartialEvaluatingExpressionTreeProcessor(new NullEvaluatableExpressionFilter()), + new TransformingExpressionTreeProcessor(tranformationProvider) + }); + } + + /// + /// Empty implementation of IEvaluatableExpressionFilter. + /// + private sealed class NullEvaluatableExpressionFilter : EvaluatableExpressionFilterBase + { + // No-op. + } } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/7c80c477/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/MethodVisitor.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/MethodVisitor.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/MethodVisitor.cs index e83c448..578c5da 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/MethodVisitor.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/MethodVisitor.cs @@ -62,9 +62,9 @@ namespace Apache.Ignite.Linq.Impl GetStringMethod("Substring", new[] {typeof (int)}, GetFunc("substring", 0, 1)), GetStringMethod("Substring", new[] {typeof (int), typeof (int)}, GetFunc("substring", 0, 1)), GetStringMethod("Trim", "trim"), - GetStringMethod("Trim", "trim", typeof(char[])), - GetStringMethod("TrimStart", "ltrim", typeof(char[])), - GetStringMethod("TrimEnd", "rtrim", typeof(char[])), + GetParameterizedTrimMethod("Trim", "trim"), + GetParameterizedTrimMethod("TrimStart", "ltrim"), + GetParameterizedTrimMethod("TrimEnd", "rtrim"), GetStringMethod("Replace", "replace", typeof(string), typeof(string)), GetMethod(typeof (Regex), "Replace", new[] {typeof (string), typeof (string), typeof (string)}, @@ -176,25 +176,59 @@ namespace Apache.Ignite.Linq.Impl if (isInstanceMethod || (i > 0)) visitor.ResultBuilder.Append(", "); - if (arg.NodeType == ExpressionType.NewArrayInit) + visitor.Visit(arg); + + AppendAdjustment(visitor, adjust, i + 1); + } + + visitor.ResultBuilder.Append(suffix).Append(")"); + + AppendAdjustment(visitor, adjust, 0); + } + + /// + /// Visits the instance function for Trim specific handling. + /// + private static void VisitParameterizedTrimFunc(MethodCallExpression expression, + CacheQueryExpressionVisitor visitor, string func) + { + visitor.ResultBuilder.Append(func).Append("("); + + visitor.Visit(expression.Object); + + var arg = expression.Arguments[0]; + + if (arg != null) + { + visitor.ResultBuilder.Append(", "); + + if (arg.NodeType == ExpressionType.Constant) { - // Only trim methods use params[], only one param is supported - var args = ((NewArrayExpression) arg).Expressions; + var constant = (ConstantExpression) arg; + var args = constant.Value as IEnumerable; + + if (args == null) + { + throw new NotSupportedException("String.Trim function only supports IEnumerable"); + } + + var enumeratedArgs = args.ToArray(); - if (args.Count != 1) - throw new NotSupportedException("Method call only supports a single parameter: "+ expression); + if (enumeratedArgs.Length != 1) + { + throw new NotSupportedException("String.Trim function only supports a single argument: " + + expression); + } - visitor.Visit(args[0]); + visitor.AppendParameter(enumeratedArgs[0]); } else + { visitor.Visit(arg); - - AppendAdjustment(visitor, adjust, i + 1); + } } - visitor.ResultBuilder.Append(suffix).Append(")"); - - AppendAdjustment(visitor, adjust, 0); + visitor.ResultBuilder.Append(")"); } /// @@ -261,6 +295,16 @@ namespace Apache.Ignite.Linq.Impl } /// + /// Gets string parameterized Trim(TrimStart, TrimEnd) method. + /// + private static KeyValuePair GetParameterizedTrimMethod(string name, + string sqlName) + { + return GetMethod(typeof(string), name, new[] {typeof(char[])}, + (e, v) => VisitParameterizedTrimFunc(e, v, sqlName)); + } + + /// /// Gets the math method. /// private static KeyValuePair GetMathMethod(string name, string sqlName,