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 2018/03/07 08:45:02 UTC
ignite git commit: IGNITE-5298 .NET: DML update via LINQ
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 <gu...@gmail.com>
Authored: Wed Mar 7 11:44:51 2018 +0300
Committer: Pavel Tupitsyn <pt...@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