Repository: ignite
Updated Branches:
  refs/heads/master 31087002b -> ed76af245


IGNITE-5298 .NET: DML update via LINQ

This closes #3599


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/ed76af24
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/ed76af24
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/ed76af24

Branch: refs/heads/master
Commit: ed76af245e7947b0e701907bde76849971a6a75c
Parents: 3108700
Author: Sergey Stronchinskiy <gurustronpub...@gmail.com>
Authored: Wed Mar 7 11:44:51 2018 +0300
Committer: Pavel Tupitsyn <ptupit...@apache.org>
Committed: Wed Mar 7 11:44:51 2018 +0300

----------------------------------------------------------------------
 .../Cache/Query/Linq/CacheLinqTest.Base.cs      |   4 +
 .../Cache/Query/Linq/CacheLinqTest.Custom.cs    | 195 +++++++++++++++++++
 .../Apache.Ignite.Linq.csproj                   |   4 +
 .../Apache.Ignite.Linq/CacheLinqExtensions.cs   |  28 +++
 .../Apache.Ignite.Linq/IUpdateDescriptor.cs     |  51 +++++
 .../Apache.Ignite.Linq/Impl/AliasDictionary.cs  |   8 +-
 .../Impl/CacheQueryExpressionVisitor.cs         |  43 +++-
 .../Impl/CacheQueryModelVisitor.cs              | 143 ++++++++++----
 .../Apache.Ignite.Linq/Impl/CacheQueryParser.cs |   3 +
 .../Impl/Dml/MemberUpdateContainer.cs           |  38 ++++
 .../Impl/Dml/UpdateAllExpressionNode.cs         | 138 +++++++++++++
 .../Impl/Dml/UpdateAllResultOperator.cs         |  75 +++++++
 12 files changed, 680 insertions(+), 50 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/ed76af24/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Base.cs
----------------------------------------------------------------------
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 d70a7a4..5b56abd 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
@@ -346,6 +346,8 @@ namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
 
             [QuerySqlField] public int AliasTest { get; set; }
 
+            [QuerySqlField] public bool Bool { get; set; }
+
             public void WriteBinary(IBinaryWriter writer)
             {
                 writer.WriteInt("age1", Age);
@@ -354,6 +356,7 @@ namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
                 writer.WriteObject("Address", Address);
                 writer.WriteTimestamp("Birthday", Birthday);
                 writer.WriteInt("AliasTest", AliasTest);
+                writer.WriteBoolean("Bool", Bool);
             }
 
             public void ReadBinary(IBinaryReader reader)
@@ -364,6 +367,7 @@ namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
                 Address = reader.ReadObject<Address>("Address");
                 Birthday = reader.ReadTimestamp("Birthday");
                 AliasTest = reader.ReadInt("AliasTest");
+                Bool = reader.ReadBoolean("Bool");
             }
         }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/ed76af24/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Custom.cs
----------------------------------------------------------------------
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Custom.cs
 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Custom.cs
index f797b4c..c854f1f 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Custom.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Custom.cs
@@ -28,6 +28,7 @@ namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
 {
     using System;
     using System.Linq;
+    using Apache.Ignite.Core.Cache;
     using Apache.Ignite.Core.Cache.Configuration;
     using Apache.Ignite.Core.Common;
     using Apache.Ignite.Linq;
@@ -103,5 +104,199 @@ namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
             var ex = Assert.Throws<IgniteException>(() => qry.RemoveAll());
             Assert.AreEqual("Failed to parse query", ex.Message.Substring(0, 
21));
         }
+
+        /// <summary>
+        /// Tests the UpdateAll extension without condition.
+        /// </summary>
+        [Test]
+        public void TestUpdateAllUnconditional()
+        {
+            // Use new cache to avoid touching static data.
+            var personCount = 10;
+            var orgCount = 3;
+            var personQueryable = 
GetPersonCacheQueryable("updateAllTest_Unconditional_Persons", personCount, 
orgCount);
+            var orgQueryable = 
GetOrgCacheQueryable("updateAllTest_Unconditional_Org", orgCount);
+
+            var allOrg = orgQueryable.ToArray();
+
+            // Constant value
+            var updated = personQueryable
+                .UpdateAll(d => d.Set(p => p.AliasTest, 7));
+            Assert.AreEqual(personCount, updated);
+            AssertAll(personQueryable, p => p.Value.AliasTest == 7);
+
+            // Expression value - from self
+            updated = personQueryable
+                .UpdateAll(d => d.Set(p => p.AliasTest, e => e.Key));
+            Assert.AreEqual(personCount, updated);
+            AssertAll(personQueryable, p => p.Value.AliasTest == p.Key);
+
+            // Multiple sets
+            var aliasValue = 3;
+            updated = personQueryable
+                .UpdateAll(d => d.Set(p => p.AliasTest, aliasValue).Set(p => 
p.Name, aliasValue.ToString()));
+            Assert.AreEqual(personCount, updated);
+            AssertAll(personQueryable, p => p.Value.AliasTest == aliasValue && 
p.Value.Name == aliasValue.ToString());
+
+            // Expression value - subquery with same cache
+            updated = personQueryable
+                .UpdateAll(d => d.Set(p => p.AliasTest,
+                    e => personQueryable.Where(ie => ie.Key == 
e.Key).Select(ie => ie.Key).First()));
+            Assert.AreEqual(personCount, updated);
+            AssertAll(personQueryable, p => p.Value.AliasTest == p.Key);
+
+            // Expression value - subquery with other cache
+            updated = personQueryable
+                .UpdateAll(d => d.Set(p => p.AliasTest, p => 
orgQueryable.Count(o => o.Value.Id > p.Key)));
+            Assert.AreEqual(personCount, updated);
+            AssertAll(personQueryable, p => p.Value.AliasTest == 
allOrg.Count(o => o.Value.Id > p.Key));
+
+            updated = personQueryable
+                .UpdateAll(d => d.Set(p => p.Name,
+                    e => orgQueryable.Where(o => o.Key == 
e.Value.OrganizationId).Select(o => o.Value.Name).First()));
+            Assert.AreEqual(personCount, updated);
+            AssertAll(personQueryable,
+                p => p.Value.Name == allOrg.Where(o => o.Key == 
p.Value.OrganizationId).Select(o => o.Value.Name)
+                         .FirstOrDefault());
+
+            // Expression value - Contains subquery with other cache
+            updated = personQueryable
+                .UpdateAll(d => d.Set(p => p.Bool,
+                    p => orgQueryable.Select(o => 
o.Key).Contains(p.Value.OrganizationId)));
+            Assert.AreEqual(personCount, updated);
+            AssertAll(personQueryable,
+                 p => p.Value.Bool == allOrg.Select(o => 
o.Key).Contains(p.Value.OrganizationId));
+
+            // Row number limit.
+            var name = "rowLimit" + 2;
+            updated = personQueryable
+                .Take(2)
+                .UpdateAll(d => d.Set(p => p.Name, name));
+            Assert.AreEqual(2, updated);
+            Assert.AreEqual(2, personQueryable.Count(p => p.Value.Name == 
name));
+        }
+
+        /// <summary>
+        /// Tests the UpdateAll extension with condition.
+        /// </summary>
+        [Test]
+        public void TestUpdateAllWithCondition()
+        {
+            // ReSharper disable AccessToModifiedClosure
+            // Use new cache to avoid touching static data.
+            var personQueryable = 
GetPersonCacheQueryable("updateAllTest_WithCondition_Persons", 10);
+
+            // Simple conditional
+            var aliasValue = 777;
+            var updated = personQueryable
+                .Where(p => p.Key > 8)
+                .UpdateAll(d => d.Set(p => p.AliasTest, aliasValue));
+            Assert.AreEqual(2, updated);
+            AssertAll(personQueryable,
+                p => p.Key <= 8 && p.Value.AliasTest != aliasValue || 
p.Value.AliasTest == aliasValue);
+
+            // Conditional with limit
+            aliasValue = 8888;
+            updated = personQueryable
+                .Where(p => p.Key > 1)
+                .Take(3)
+                .UpdateAll(d => d.Set(p => p.AliasTest, aliasValue));
+            Assert.AreEqual(3, updated);
+            Assert.AreEqual(3, personQueryable.ToArray().Count(p => 
p.Value.AliasTest == aliasValue));
+            // ReSharper restore AccessToModifiedClosure
+        }
+
+        /// <summary>
+        /// Tests not supported queries for the UpdateAll extension .
+        /// </summary>
+        [Test]
+        public void TestUpdateAllUnsupported()
+        {
+            // Use new cache to avoid touching static data.
+            var personQueryable = 
GetPersonCacheQueryable("updateAllTest_Unsupported_Persons", 10);
+            
+            // Skip is not supported with DELETE.
+            var nex = Assert.Throws<NotSupportedException>(
+                () => personQueryable.Skip(1).UpdateAll(d => d.Set(p => p.Age, 
15)));
+            Assert.AreEqual("UpdateAll can not be combined with result 
operators (other than Take): SkipResultOperator",
+                nex.Message);
+
+            // Multiple result operators are not supported with DELETE.
+            nex = Assert.Throws<NotSupportedException>(() =>
+                personQueryable.Skip(1).Take(1).UpdateAll(d => d.Set(p => 
p.Age, 15)));
+            Assert.AreEqual(
+                "UpdateAll can not be combined with result operators (other 
than Take): SkipResultOperator, " +
+                "TakeResultOperator, UpdateAllResultOperator", nex.Message);
+
+            // Joins are not supported in H2.
+            var qry = personQueryable
+                .Where(x => x.Key == 7)
+                .Join(GetPersonCache().AsCacheQueryable(), p => p.Key, p => 
p.Key, (p1, p2) => p1);
+
+            var ex = Assert.Throws<IgniteException>(() => qry.UpdateAll(d => 
d.Set(p => p.Age, 15)));
+            Assert.AreEqual("Failed to parse query", ex.Message.Substring(0, 
21));
+        }
+
+        /// <summary>
+        /// Gets filled persons cache  queryable
+        /// </summary>
+        private IQueryable<ICacheEntry<int, Person>> 
GetPersonCacheQueryable(string cacheName, int personCount,
+            int? orgCount = null)
+        {
+            var cache = GetCache<Person>(cacheName);
+
+            cache.PutAll(Enumerable.Range(1, personCount).ToDictionary(x => x,
+                x => new Person(x, x.ToString())
+                {
+                    Birthday = DateTime.UtcNow.AddDays(personCount - x),
+                    OrganizationId = x % orgCount ?? 0
+                }));
+
+            return cache.AsCacheQueryable();
+        }
+
+        /// <summary>
+        /// Asserts that all values in cache correspond to predicate
+        /// </summary>
+        private static void AssertAll(
+            // ReSharper disable once 
ParameterOnlyUsedForPreconditionCheck.Local
+            IQueryable<ICacheEntry<int, Person>> personQueryable, 
+            // ReSharper disable once 
ParameterOnlyUsedForPreconditionCheck.Local
+            Func<ICacheEntry<int, Person>, bool> predicate)
+        {
+            Assert.IsTrue(personQueryable.ToArray().All(predicate));
+        }
+
+        /// <summary>
+        /// Gets filled organization cache queryable
+        /// </summary>
+        private IQueryable<ICacheEntry<int, Organization>> 
GetOrgCacheQueryable(string cacheName, int orgCount)
+        {
+            var cache = GetCache<Organization>(cacheName);
+            var allOrg = Enumerable.Range(1, orgCount)
+                .Select(x => new Organization
+                {
+                    Id = x,
+                    Name = x.ToString()
+                })
+                .ToList();
+
+            allOrg.ForEach(x => cache.Put(x.Id, x));
+
+            return cache.AsCacheQueryable();
+        }
+
+        /// <summary>
+        /// Gets cache of <see cref="T"/>
+        /// </summary>
+        private ICache<int, T> GetCache<T>(string cacheName)
+        {
+            return Ignition.GetIgnite().GetOrCreateCache<int, T>(new 
CacheConfiguration(cacheName,
+                new QueryEntity(typeof(int), typeof(T)))
+            {
+                SqlEscapeAll = GetSqlEscapeAll(),
+                CacheMode = CacheMode.Replicated
+            });
+        }
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/ed76af24/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.csproj
----------------------------------------------------------------------
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.csproj 
b/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.csproj
index fc13914..d5a2d83 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.csproj
@@ -64,7 +64,10 @@
     <Compile Include="Impl\CacheQueryExpressionVisitor.cs" />
     <Compile Include="Impl\CacheQueryModelVisitor.cs" />
     <Compile Include="Impl\CacheQueryParser.cs" />
+    <Compile Include="Impl\Dml\MemberUpdateContainer.cs" />
+    <Compile Include="Impl\Dml\UpdateAllExpressionNode.cs" />
     <Compile Include="Impl\Dml\RemoveAllExpressionNode.cs" />
+    <Compile Include="Impl\Dml\UpdateAllResultOperator.cs" />
     <Compile Include="Impl\Dml\RemoveAllResultOperator.cs" />
     <Compile Include="Impl\EnumerableHelper.cs" />
     <Compile Include="Impl\ICacheQueryableInternal.cs" />
@@ -73,6 +76,7 @@
     <Compile Include="Impl\SqlTypes.cs" />
     <Compile Include="Impl\ExpressionWalker.cs" />
     <Compile 
Include="Impl\JoinInnerSequenceParameterNotNullExpressionVisitor.cs" />
+    <Compile Include="IUpdateDescriptor.cs" />
     <Compile Include="Package-Info.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="QueryOptions.cs" />

http://git-wip-us.apache.org/repos/asf/ignite/blob/ed76af24/modules/platforms/dotnet/Apache.Ignite.Linq/CacheLinqExtensions.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/CacheLinqExtensions.cs 
b/modules/platforms/dotnet/Apache.Ignite.Linq/CacheLinqExtensions.cs
index 935666f..2b65d85 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Linq/CacheLinqExtensions.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/CacheLinqExtensions.cs
@@ -193,5 +193,33 @@ namespace Apache.Ignite.Linq
             return query.Provider.Execute<int>(Expression.Call(null, method, 
query.Expression,
                 Expression.Quote(predicate)));
         }
+
+        /// <summary>
+        /// Updates all rows that are matched by the specified query.
+        /// <para />
+        /// This method results in "UPDATE" distributed SQL query, performing 
bulk update 
+        /// (as opposed to fetching all rows locally).
+        /// </summary>
+        /// <typeparam name="TKey">Key type.</typeparam>
+        /// <typeparam name="TValue">Value type.</typeparam>
+        /// <param name="query">The query.</param>
+        /// <param name="updateDescription">The update description.</param>
+        /// <returns>Affected row count.</returns>
+        public static int UpdateAll<TKey, TValue>(this 
IQueryable<ICacheEntry<TKey, TValue>> query,
+            Expression<Func<IUpdateDescriptor<TKey,TValue>, 
IUpdateDescriptor<TKey,TValue>>> updateDescription)
+        {
+            IgniteArgumentCheck.NotNull(query, "query");
+            IgniteArgumentCheck.NotNull(updateDescription, 
"updateDescription");
+
+            var method = UpdateAllExpressionNode.UpdateAllMethodInfo
+                .MakeGenericMethod(typeof(TKey), typeof(TValue)); // TODO: 
cache?
+
+            return query.Provider.Execute<int>(Expression.Call(null, method, 
new[]
+            {
+                query.Expression,
+                Expression.Quote(updateDescription)
+            }));
+        }
+
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/ed76af24/modules/platforms/dotnet/Apache.Ignite.Linq/IUpdateDescriptor.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/IUpdateDescriptor.cs 
b/modules/platforms/dotnet/Apache.Ignite.Linq/IUpdateDescriptor.cs
new file mode 100644
index 0000000..2327b99
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/IUpdateDescriptor.cs
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Linq
+{
+    using System;
+    using Apache.Ignite.Core.Cache;
+
+    /// <summary>
+    /// Dummy interface to provide update description for
+    /// <see
+    ///     cref="CacheLinqExtensions.UpdateAll{TKey,TValue}" />
+    /// </summary>
+    /// <typeparam name="TKey">Key type.</typeparam>
+    /// <typeparam name="TValue">Value type.</typeparam>
+    public interface IUpdateDescriptor<out TKey, out TValue>
+    {
+        /// <summary>
+        /// Specifies member update with constant
+        /// </summary>
+        /// <typeparam name="TProp">Member type</typeparam>
+        /// <param name="selector">Member selector</param>
+        /// <param name="value">New value</param>
+        /// <returns></returns>
+        IUpdateDescriptor<TKey, TValue> Set<TProp>(Func<TValue, TProp> 
selector, TProp value);
+
+        /// <summary>
+        /// Specifies member update with expression
+        /// </summary>
+        /// <typeparam name="TProp">Member type</typeparam>
+        /// <param name="selector">Member selector</param>
+        /// <param name="valueBuilder">New value generator</param>
+        /// <returns></returns>
+        IUpdateDescriptor<TKey, TValue> Set<TProp>(Func<TValue, TProp> 
selector,
+            Func<ICacheEntry<TKey, TValue>, TProp> valueBuilder);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/ed76af24/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/AliasDictionary.cs
----------------------------------------------------------------------
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/AliasDictionary.cs 
b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/AliasDictionary.cs
index 8902e7c..15710db 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/AliasDictionary.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/AliasDictionary.cs
@@ -20,6 +20,7 @@ namespace Apache.Ignite.Linq.Impl
     using System;
     using System.Collections.Generic;
     using System.Diagnostics;
+    using System.Linq;
     using System.Linq.Expressions;
     using System.Text;
     using Remotion.Linq.Clauses;
@@ -49,11 +50,14 @@ namespace Apache.Ignite.Linq.Impl
         /// <summary>
         /// Pushes current aliases to stack.
         /// </summary>
-        public void Push()
+        /// <param name="copyAliases">Flag indicating that current aliases 
should be copied</param>
+        public void Push(bool copyAliases)
         {
             _stack.Push(_tableAliases);
 
-            _tableAliases = new Dictionary<IQuerySource, string>();
+            _tableAliases = copyAliases
+                ? _tableAliases.ToDictionary(p => p.Key, p => p.Value)
+                : new Dictionary<IQuerySource, string>();
         }
 
         /// <summary>

http://git-wip-us.apache.org/repos/asf/ignite/blob/ed76af24/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 4caefe1..c5c887f 100644
--- 
a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs
@@ -54,21 +54,34 @@ namespace Apache.Ignite.Linq.Impl
         /** */
         private readonly bool _includeAllFields;
 
+        /** */
+        private readonly bool _visitEntireSubQueryModel;
+
         /// <summary>
         /// Initializes a new instance of the <see 
cref="CacheQueryExpressionVisitor" /> class.
         /// </summary>
         /// <param name="modelVisitor">The _model visitor.</param>
-        /// <param name="useStar">Flag indicating that star '*' qualifier 
should be used
-        /// for the whole-table select instead of _key, _val.</param>
-        /// <param name="includeAllFields">Flag indicating that star '*' 
qualifier should be used
-        /// for the whole-table select as well as _key, _val.</param>
-        public CacheQueryExpressionVisitor(CacheQueryModelVisitor 
modelVisitor, bool useStar, bool includeAllFields)
+        /// <param name="useStar">
+        /// Flag indicating that star '*' qualifier should be used
+        /// for the whole-table select instead of _key, _val.
+        /// </param>
+        /// <param name="includeAllFields">
+        /// Flag indicating that star '*' qualifier should be used
+        /// for the whole-table select as well as _key, _val.
+        /// </param>
+        /// <param name="visitEntireSubQueryModel">
+        /// Flag indicating that subquery 
+        /// should be visited as full query
+        /// </param>
+        public CacheQueryExpressionVisitor(CacheQueryModelVisitor 
modelVisitor, bool useStar, bool includeAllFields,
+            bool visitEntireSubQueryModel)
         {
             Debug.Assert(modelVisitor != null);
 
             _modelVisitor = modelVisitor;
             _useStar = useStar;
             _includeAllFields = includeAllFields;
+            _visitEntireSubQueryModel = visitEntireSubQueryModel;
         }
 
         /// <summary>
@@ -548,12 +561,18 @@ namespace Apache.Ignite.Linq.Impl
             var subQueryModel = expression.QueryModel;
 
             var contains = subQueryModel.ResultOperators.FirstOrDefault() as 
ContainsResultOperator;
-            
+
             // Check if IEnumerable.Contains is used.
-            if (subQueryModel.ResultOperators.Count == 1 && contains != null)
+            if (subQueryModel.ResultOperators.Count == 1 && contains != null) 
             {
                 VisitContains(subQueryModel, contains);
             }
+            else if (_visitEntireSubQueryModel)
+            {
+                ResultBuilder.Append("(");
+                _modelVisitor.VisitQueryModel(subQueryModel, false, true);
+                ResultBuilder.Append(")");
+            }
             else
             {
                 // This happens when New expression uses a subquery, in a 
GroupBy.
@@ -579,7 +598,15 @@ namespace Apache.Ignite.Linq.Impl
                 Visit(contains.Item);
 
                 ResultBuilder.Append(" IN (");
-                _modelVisitor.VisitQueryModel(subQueryModel);
+                if (_visitEntireSubQueryModel)
+                {
+                    _modelVisitor.VisitQueryModel(subQueryModel, false, true);
+                }
+                else
+                {
+                    _modelVisitor.VisitQueryModel(subQueryModel);
+                }
+                
                 ResultBuilder.Append(")");
             }
             else

http://git-wip-us.apache.org/repos/asf/ignite/blob/ed76af24/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryModelVisitor.cs
----------------------------------------------------------------------
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryModelVisitor.cs 
b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryModelVisitor.cs
index 223eea6..da250e9 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryModelVisitor.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryModelVisitor.cs
@@ -103,26 +103,30 @@ namespace Apache.Ignite.Linq.Impl
         /// <summary>
         /// Visits the query model.
         /// </summary>
-        private void VisitQueryModel(QueryModel queryModel, bool 
includeAllFields)
+        internal void VisitQueryModel(QueryModel queryModel, bool 
includeAllFields, bool copyAliases = false)
         {
-            _aliases.Push();
+            _aliases.Push(copyAliases);
 
-            var hasDelete = VisitRemoveOperator(queryModel);
-
-            if (!hasDelete)
+            var lastResultOp = queryModel.ResultOperators.LastOrDefault();
+            if (lastResultOp is RemoveAllResultOperator)
+            {
+                VisitRemoveOperator(queryModel);
+            }
+            else if(lastResultOp is UpdateAllResultOperator)
+            {
+                VisitUpdateAllOperator(queryModel);
+            }
+            else
             {
                 // SELECT
                 _builder.Append("select ");
 
                 // TOP 1 FLD1, FLD2
                 VisitSelectors(queryModel, includeAllFields);
-            }
 
-            // FROM ... WHERE ... JOIN ...
-            base.VisitQueryModel(queryModel);
+                // FROM ... WHERE ... JOIN ...
+                base.VisitQueryModel(queryModel);
 
-            if (!hasDelete)
-            {
                 // UNION ...
                 ProcessResultOperatorsEnd(queryModel);
             }
@@ -131,42 +135,68 @@ namespace Apache.Ignite.Linq.Impl
         }
 
         /// <summary>
-        /// Visits the remove operator. Returns true if it is present.
+        /// Visits the remove operator.
         /// </summary>
-        private bool VisitRemoveOperator(QueryModel queryModel)
+        private void VisitRemoveOperator(QueryModel queryModel)
         {
             var resultOps = queryModel.ResultOperators;
 
-            if (resultOps.LastOrDefault() is RemoveAllResultOperator)
+            _builder.Append("delete ");
+
+            if (resultOps.Count == 2)
             {
-                _builder.Append("delete ");
+                var resOp = resultOps[0] as TakeResultOperator;
 
-                if (resultOps.Count == 2)
-                {
-                    var resOp = resultOps[0] as TakeResultOperator;
+                if (resOp == null)
+                    throw new NotSupportedException(
+                        "RemoveAll can not be combined with result operators 
(other than Take): " +
+                        resultOps[0].GetType().Name);
 
-                    if (resOp == null)
-                    {
-                        throw new NotSupportedException(
-                            "RemoveAll can not be combined with result 
operators (other than Take): " +
-                            resultOps[0].GetType().Name);
-                    }
+                _builder.Append("top ");
+                BuildSqlExpression(resOp.Count);
+                _builder.Append(" ");
+            }
+            else if (resultOps.Count > 2)
+            {
+                throw new NotSupportedException(
+                    "RemoveAll can not be combined with result operators 
(other than Take): " +
+                    string.Join(", ", resultOps.Select(x => 
x.GetType().Name)));
+            }
 
-                    _builder.Append("top ");
-                    BuildSqlExpression(resOp.Count);
-                    _builder.Append(" ");
-                }
-                else if (resultOps.Count > 2)
-                {
+            // FROM ... WHERE ... JOIN ...
+            base.VisitQueryModel(queryModel);
+        }
+
+        /// <summary>
+        /// Visits the UpdateAll operator.
+        /// </summary>
+        private void VisitUpdateAllOperator(QueryModel queryModel)
+        {
+            var resultOps = queryModel.ResultOperators;
+
+            _builder.Append("update ");
+
+            // FROM ... WHERE ...
+            base.VisitQueryModel(queryModel);
+
+            if (resultOps.Count == 2)
+            {
+                var resOp = resultOps[0] as TakeResultOperator;
+
+                if (resOp == null)
                     throw new NotSupportedException(
-                        "RemoveAll can not be combined with result operators 
(other than Take): " +
-                        string.Join(", ", resultOps.Select(x => 
x.GetType().Name)));
-                }
+                        "UpdateAll can not be combined with result operators 
(other than Take): " +
+                        resultOps[0].GetType().Name);
 
-                return true;
+                _builder.Append("limit ");
+                BuildSqlExpression(resOp.Count);
+            }
+            else if (resultOps.Count > 2)
+            {
+                throw new NotSupportedException(
+                    "UpdateAll can not be combined with result operators 
(other than Take): " +
+                    string.Join(", ", resultOps.Select(x => 
x.GetType().Name)));
             }
-                
-            return false;
         }
 
         /// <summary>
@@ -394,18 +424,28 @@ namespace Apache.Ignite.Linq.Impl
         {
             base.VisitMainFromClause(fromClause, queryModel);
 
-            _builder.AppendFormat("from ");
+            var isUpdateQuery = queryModel.ResultOperators.LastOrDefault() is 
UpdateAllResultOperator;
+            if (!isUpdateQuery)
+            {
+                _builder.Append("from ");
+            }
+
             ValidateFromClause(fromClause);
             _aliases.AppendAsClause(_builder, fromClause).Append(" ");
 
             var i = 0;
             foreach (var additionalFrom in 
queryModel.BodyClauses.OfType<AdditionalFromClause>())
             {
-                _builder.AppendFormat(", ");
+                _builder.Append(", ");
                 ValidateFromClause(additionalFrom);
 
                 VisitAdditionalFromClause(additionalFrom, queryModel, i++);
             }
+
+            if (isUpdateQuery)
+            {
+                BuildSetClauseForUpdateAll(queryModel);
+            }
         }
 
         /// <summary>
@@ -535,7 +575,6 @@ namespace Apache.Ignite.Linq.Impl
             }
         }
 
-
         /// <summary>
         /// Visists Join clause in case of join with local collection
         /// </summary>
@@ -651,9 +690,33 @@ namespace Apache.Ignite.Linq.Impl
         /// <summary>
         /// Builds the SQL expression.
         /// </summary>
-        private void BuildSqlExpression(Expression expression, bool useStar = 
false, bool includeAllFields = false)
+        private void BuildSqlExpression(Expression expression, bool useStar = 
false, bool includeAllFields = false,
+            bool visitSubqueryModel = false)
+        {
+            new CacheQueryExpressionVisitor(this, useStar, includeAllFields, 
visitSubqueryModel).Visit(expression);
+        }
+
+        /// <summary>
+        /// Builds SET clause of UPDATE statement
+        /// </summary>
+        private void BuildSetClauseForUpdateAll(QueryModel queryModel)
         {
-            new CacheQueryExpressionVisitor(this, useStar, 
includeAllFields).Visit(expression);
+            var updateAllResultOperator = 
queryModel.ResultOperators.LastOrDefault() as UpdateAllResultOperator;
+            if (updateAllResultOperator != null)
+            {
+                _builder.Append("set ");
+                var first = true;
+                foreach (var update in updateAllResultOperator.Updates)
+                {
+                    if (!first) _builder.Append(", ");
+                    first = false;
+                    BuildSqlExpression(update.Selector);
+                    _builder.Append(" = ");
+                    BuildSqlExpression(update.Value, visitSubqueryModel: true);
+                }
+
+                _builder.Append(" ");
+            }
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/ed76af24/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 17ec0a3..3f69cce 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryParser.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryParser.cs
@@ -66,6 +66,9 @@ namespace Apache.Ignite.Linq.Impl
             
methodInfoRegistry.Register(RemoveAllExpressionNode.GetSupportedMethods(), 
                 typeof(RemoveAllExpressionNode));
 
+            
methodInfoRegistry.Register(UpdateAllExpressionNode.GetSupportedMethods(),
+                typeof(UpdateAllExpressionNode));
+
             return new CompoundNodeTypeProvider(new INodeTypeProvider[]
             {
                 methodInfoRegistry,

http://git-wip-us.apache.org/repos/asf/ignite/blob/ed76af24/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/Dml/MemberUpdateContainer.cs
----------------------------------------------------------------------
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/Dml/MemberUpdateContainer.cs 
b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/Dml/MemberUpdateContainer.cs
new file mode 100644
index 0000000..bf8a5fd
--- /dev/null
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/Dml/MemberUpdateContainer.cs
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+namespace Apache.Ignite.Linq.Impl.Dml
+{
+    using System.Linq.Expressions;
+
+    /// <summary>
+    /// Contains information about member update
+    /// </summary>
+    internal struct MemberUpdateContainer
+    {
+        /// <summary>
+        /// Gets or sets member selector
+        /// </summary>
+        public Expression Selector { get; set; }
+
+        /// <summary>
+        /// Gets or sets member new value
+        /// </summary>
+        public Expression Value { get; set; }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/ed76af24/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/Dml/UpdateAllExpressionNode.cs
----------------------------------------------------------------------
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/Dml/UpdateAllExpressionNode.cs
 
b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/Dml/UpdateAllExpressionNode.cs
new file mode 100644
index 0000000..8cfb552
--- /dev/null
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/Dml/UpdateAllExpressionNode.cs
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Linq.Impl.Dml
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics.CodeAnalysis;
+    using System.Linq;
+    using System.Linq.Expressions;
+    using System.Reflection;
+    using Remotion.Linq.Clauses;
+    using Remotion.Linq.Clauses.Expressions;
+    using Remotion.Linq.Parsing.ExpressionVisitors;
+    using Remotion.Linq.Parsing.Structure.IntermediateModel;
+
+    /// <summary>
+    /// Represents a <see cref="MethodCallExpression" /> for
+    /// <see cref="CacheLinqExtensions.UpdateAll{TKey,TValue}" />.
+    /// When user calls UpdateAll, this node is generated.
+    /// </summary>
+    internal sealed class UpdateAllExpressionNode : 
ResultOperatorExpressionNodeBase
+    {
+        /// <summary>
+        /// The UpdateAll method.
+        /// </summary>
+        public static readonly MethodInfo UpdateAllMethodInfo = 
typeof(CacheLinqExtensions)
+            .GetMethods()
+            .Single(x => x.Name == "UpdateAll");
+
+        //** */
+        private static readonly MethodInfo[] SupportedMethods = 
{UpdateAllMethodInfo};
+
+        //** */
+        private readonly LambdaExpression _updateDescription;
+
+        /// <summary>
+        /// Initializes a new instance of the <see 
cref="UpdateAllExpressionNode" /> class.
+        /// </summary>
+        /// <param name="parseInfo">The parse information.</param>
+        /// <param name="updateDescription">Expression with update description 
info</param>
+        public UpdateAllExpressionNode(MethodCallExpressionParseInfo parseInfo,
+            LambdaExpression updateDescription)
+            : base(parseInfo, null, null)
+        {
+            _updateDescription = updateDescription;
+        }
+
+        /** <inheritdoc /> */
+        [ExcludeFromCodeCoverage]
+        public override Expression Resolve(ParameterExpression inputParameter, 
Expression expressionToBeResolved,
+            ClauseGenerationContext clauseGenerationContext)
+        {
+            throw CreateResolveNotSupportedException();
+        }
+
+        /** <inheritdoc /> */
+        protected override ResultOperatorBase 
CreateResultOperator(ClauseGenerationContext clauseGenerationContext)
+        {
+            if (_updateDescription.Parameters.Count != 1)
+                throw new NotSupportedException("Expression is not supported 
for UpdateAll: " +
+                                                _updateDescription);
+
+            var querySourceRefExpression = (QuerySourceReferenceExpression) 
Source.Resolve(
+                _updateDescription.Parameters[0],
+                _updateDescription.Parameters[0],
+                clauseGenerationContext);
+
+            var cacheEntryType = querySourceRefExpression.Type;
+            var querySourceAccessValue =
+                Expression.MakeMemberAccess(querySourceRefExpression, 
cacheEntryType.GetMember("Value").First());
+
+            if (!(_updateDescription.Body is MethodCallExpression))
+                throw new NotSupportedException("Expression is not supported 
for UpdateAll: " +
+                                                _updateDescription.Body);
+
+            var updates = new List<MemberUpdateContainer>();
+
+            var methodCall = (MethodCallExpression) _updateDescription.Body;
+            while (methodCall != null)
+            {
+                if (methodCall.Arguments.Count != 2)
+                    throw new NotSupportedException("Method is not supported 
for UpdateAll: " + methodCall);
+
+                var selectorLambda = (LambdaExpression) 
methodCall.Arguments[0];
+                var selector = 
ReplacingExpressionVisitor.Replace(selectorLambda.Parameters[0], 
querySourceAccessValue,
+                    selectorLambda.Body);
+
+                var newValue = methodCall.Arguments[1];
+                switch (newValue.NodeType)
+                {
+                    case ExpressionType.Constant:
+                        break;
+                    case ExpressionType.Lambda:
+                        var newValueLambda = (LambdaExpression) newValue;
+                        newValue = 
ReplacingExpressionVisitor.Replace(newValueLambda.Parameters[0],
+                            querySourceRefExpression, newValueLambda.Body);
+                        break;
+                    default:
+                        throw new NotSupportedException("Value expression is 
not supported for UpdateAll: "
+                                                        + newValue);
+                }
+
+                updates.Add(new MemberUpdateContainer
+                {
+                    Selector = selector,
+                    Value = newValue
+                });
+
+                methodCall = methodCall.Object as MethodCallExpression;
+            }
+
+            return new UpdateAllResultOperator(updates);
+        }
+
+        /// <summary>
+        /// Gets the supported methods.
+        /// </summary>
+        public static IEnumerable<MethodInfo> GetSupportedMethods()
+        {
+            return SupportedMethods;
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/ed76af24/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/Dml/UpdateAllResultOperator.cs
----------------------------------------------------------------------
diff --git 
a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/Dml/UpdateAllResultOperator.cs
 
b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/Dml/UpdateAllResultOperator.cs
new file mode 100644
index 0000000..be682b0
--- /dev/null
+++ 
b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/Dml/UpdateAllResultOperator.cs
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Apache.Ignite.Linq.Impl.Dml
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics.CodeAnalysis;
+    using System.Linq;
+    using System.Linq.Expressions;
+    using Remotion.Linq.Clauses;
+    using Remotion.Linq.Clauses.ResultOperators;
+    using Remotion.Linq.Clauses.StreamedData;
+
+    /// <summary>
+    /// Represents an operator for <see 
cref="CacheLinqExtensions.UpdateAll{TKey,TValue}"/>.
+    /// </summary>
+    internal sealed class UpdateAllResultOperator : 
ValueFromSequenceResultOperatorBase
+    {
+        /// <summary>
+        /// Gets the members updates
+        /// </summary>
+        public MemberUpdateContainer[] Updates { get; private set; }
+
+        /// <summary>
+        /// Initializes a new instance of <see cref="UpdateAllResultOperator" 
/>
+        /// </summary>
+        /// <param name="updates">members updates</param>
+        public UpdateAllResultOperator(IEnumerable<MemberUpdateContainer> 
updates)
+        {
+            Updates = updates.ToArray();
+        }
+
+        /** <inheritdoc /> */
+        public override IStreamedDataInfo GetOutputDataInfo(IStreamedDataInfo 
inputInfo)
+        {
+            return new StreamedScalarValueInfo(typeof(int));
+        }
+
+        /** <inheritdoc /> */
+        [ExcludeFromCodeCoverage]
+        public override ResultOperatorBase Clone(CloneContext cloneContext)
+        {
+            return new UpdateAllResultOperator(Updates);
+        }
+
+        /** <inheritdoc /> */
+        [ExcludeFromCodeCoverage]
+        public override void TransformExpressions(Func<Expression, Expression> 
transformation)
+        {
+            // No-op.
+        }
+
+        /** <inheritdoc /> */
+        [ExcludeFromCodeCoverage]
+        public override StreamedValue ExecuteInMemory<T>(StreamedSequence 
sequence)
+        {
+            throw new NotSupportedException("UpdateAll is not supported for 
in-memory sequences.");
+        }
+    }
+}
\ No newline at end of file

Reply via email to