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 2017/06/14 15:24:26 UTC
ignite git commit: IGNITE-4636 .NET: Support local collection joins
in LINQ
Repository: ignite
Updated Branches:
refs/heads/master 6283bd83e -> b6ad6c055
IGNITE-4636 .NET: Support local collection joins in LINQ
This closes #2118
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/b6ad6c05
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/b6ad6c05
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/b6ad6c05
Branch: refs/heads/master
Commit: b6ad6c055a1870945222b4b6a49ca7b670a829fe
Parents: 6283bd8
Author: Sergey Stronchinskiy <gu...@gmail.com>
Authored: Wed Jun 14 18:24:07 2017 +0300
Committer: Pavel Tupitsyn <pt...@apache.org>
Committed: Wed Jun 14 18:24:07 2017 +0300
----------------------------------------------------------------------
.../Cache/Query/CacheLinqTest.cs | 162 ++++++++++++++++++-
.../Apache.Ignite.Linq.csproj | 2 +
.../dotnet/Apache.Ignite.Linq/CompiledQuery.cs | 31 ++--
.../Apache.Ignite.Linq/Impl/AliasDictionary.cs | 65 ++++++--
.../Impl/CacheQueryExpressionVisitor.cs | 71 +++-----
.../Impl/CacheQueryModelVisitor.cs | 96 +++++++++--
.../Apache.Ignite.Linq/Impl/EnumerableHelper.cs | 59 +++++++
.../Apache.Ignite.Linq/Impl/ExpressionWalker.cs | 86 +++++++++-
...SequenceParameterNotNullExpressionVisitor.cs | 72 +++++++++
9 files changed, 548 insertions(+), 96 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ignite/blob/b6ad6c05/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 5d52467..9c38871 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
@@ -548,17 +548,167 @@ namespace Apache.Ignite.Core.Tests.Cache.Query
}
/// <summary>
+ /// Tests the join with local collection.
+ /// </summary>
+ [Test]
+ public void TestLocalJoin()
+ {
+ var persons = GetPersonCache().AsCacheQueryable();
+ var orgs = GetOrgCache().AsCacheQueryable();
+
+ var localOrgs = orgs
+ .Select(e => e.Value)
+ .ToArray();
+
+ var allOrganizationIds = localOrgs
+ .Select(e => e.Id)
+ .ToArray();
+
+ // Join with local collection
+ var qry1 = persons.Join(allOrganizationIds,
+ pe => pe.Value.OrganizationId,
+ i => i,
+ (pe, o) => pe
+ )
+ .ToArray();
+
+ Assert.AreEqual(PersonCount, qry1.Length);
+
+ // Join using expression in innerKeySelector
+ var qry2 = persons.Join(allOrganizationIds,
+ pe => pe.Value.OrganizationId,
+ i => i + 1 - 1,
+ (pe, o) => pe
+ )
+ .ToArray();
+
+ Assert.AreEqual(PersonCount, qry2.Length);
+
+ // Local collection subquery
+ var qry3 = persons.Join(localOrgs.Select(e => e.Id),
+ pe => pe.Value.OrganizationId,
+ i => i,
+ (pe, o) => pe
+ )
+ .ToArray();
+
+ Assert.AreEqual(PersonCount, qry3.Length);
+
+ // Compiled query
+ var qry4 = CompiledQuery.Compile(() => persons.Join(allOrganizationIds,
+ pe => pe.Value.OrganizationId,
+ i => i,
+ (pe, o) => pe
+ ));
+
+ Assert.AreEqual(PersonCount, qry4().Count());
+
+ // Compiled query with outer join
+ var qry4A = CompiledQuery.Compile(() => persons.Join(new int[] {}.DefaultIfEmpty(),
+ pe => pe.Value.OrganizationId,
+ i => i,
+ (pe, o) => pe
+ ));
+
+ Assert.AreEqual(PersonCount, qry4A().Count());
+
+ // Compiled query
+ var qry5 = CompiledQuery.Compile(() => persons.Join(new[] { -1, -2 }.DefaultIfEmpty(),
+ pe => pe.Value.OrganizationId,
+ i => i,
+ (pe, o) => pe
+ ));
+
+ Assert.AreEqual(PersonCount, qry5().Count());
+
+ // Outer join
+ var qry6 = persons.Join(new[] { -1, -2 }.DefaultIfEmpty(),
+ pe => pe.Value.OrganizationId,
+ i => i,
+ (pe, o) => pe
+ )
+ .ToArray();
+
+ Assert.AreEqual(PersonCount, qry6.Length);
+
+ // Join with local list
+ var qry7 = persons.Join(new List<int> {1000, 1001, 1002, 1003},
+ pe => pe.Value.OrganizationId,
+ i => i,
+ (pe, o) => pe
+ )
+ .ToArray();
+
+ Assert.AreEqual(PersonCount, qry7.Length);
+
+ // Join with local list variable
+ var list = new List<int> { 1000, 1001, 1002, 1003 };
+ var qry8 = persons.Join(list,
+ pe => pe.Value.OrganizationId,
+ i => i,
+ (pe, o) => pe
+ )
+ .ToArray();
+
+ Assert.AreEqual(PersonCount, qry8.Length);
+ }
+
+ /// <summary>
+ /// Tests the compiled query containing join with local collection passed as parameter.
+ /// </summary>
+ [Test]
+ [Ignore("IGNITE-5404")]
+ public void TestLocalJoinCompiledQueryParameter()
+ {
+ var persons = GetPersonCache().AsCacheQueryable();
+ var orgs = GetOrgCache().AsCacheQueryable();
+
+ var localOrgs = orgs
+ .Select(e => e.Value)
+ .ToArray();
+
+ var allOrganizationIds = localOrgs
+ .Select(e => e.Id)
+ .ToArray();
+
+ // Join with local collection passed as parameter
+ var qry1 = CompiledQuery.Compile((IEnumerable<int> lc) => persons.Join(lc,
+ pe => pe.Value.OrganizationId,
+ i => i,
+ (pe, o) => pe
+ ));
+
+ Assert.AreEqual(PersonCount, qry1(allOrganizationIds).Count());
+
+ //Compiled query with outer join
+ var qry2 = CompiledQuery.Compile((IEnumerable<int> lc) => persons.Join(lc.DefaultIfEmpty(),
+ pe => pe.Value.OrganizationId,
+ i => i,
+ (pe, o) => pe
+ ));
+
+ Assert.AreEqual(PersonCount, qry2(new[] { -11 }).Count());
+ }
+
+ /// <summary>
/// Tests the invalid join.
/// </summary>
[Test]
public void TestInvalidJoin()
{
- // Join on non-IQueryable
+ var localComplexTypeCollection = GetOrgCache().AsCacheQueryable()
+ .Select(e => e.Value)
+ .ToArray();
+
+ // Join on non-IQueryable with complex(not supported) type
var ex = Assert.Throws<NotSupportedException>(() =>
// ReSharper disable once ReturnValueOfPureMethodIsNotUsed
- GetPersonCache().AsCacheQueryable().Join(GetOrgCache(), p => p.Key, o => o.Key, (p, o) => p).ToList());
+ {
+ GetPersonCache().AsCacheQueryable().Join(localComplexTypeCollection, p => p.Value.OrganizationId,
+ o => o.Id, (p, o) => p).ToList();
+ });
- Assert.IsTrue(ex.Message.StartsWith("Unexpected query source"));
+ Assert.IsTrue(ex.Message.StartsWith("Not supported item type for Join with local collection"));
}
/// <summary>
@@ -810,6 +960,9 @@ namespace Apache.Ignite.Core.Tests.Cache.Query
var aLotOfKeys = Enumerable.Range(-bigNumberOfKeys + 10 - PersonCount, bigNumberOfKeys + PersonCount)
.ToArray();
var hashSetKeys = new HashSet<int>(keys);
+ var defferedCollection = Enumerable.Range(1, 10)
+ .Select(i => new {Id = i})
+ .Select(arg => arg.Id);
CheckWhereFunc(cache, e => new[] { 1, 2, 3 }.Contains(e.Key));
CheckWhereFunc(cache, e => emptyKeys.Contains(e.Key));
@@ -820,6 +973,7 @@ namespace Apache.Ignite.Core.Tests.Cache.Query
CheckWhereFunc(cache, e => aLotOfKeys.Contains(e.Key));
CheckWhereFunc(cache, e => hashSetKeys.Contains(e.Key));
CheckWhereFunc(cache, e => !keys.Contains(e.Key));
+ CheckWhereFunc(cache, e => defferedCollection.Contains(e.Key));
CheckWhereFunc(orgCache, e => new[] { "Org_1", "NonExistentName", null }.Contains(e.Value.Name));
CheckWhereFunc(orgCache, e => !new[] { "Org_1", "NonExistentName", null }.Contains(e.Value.Name));
CheckWhereFunc(orgCache, e => new[] { "Org_1", null, null }.Contains(e.Value.Name));
@@ -859,7 +1013,7 @@ namespace Apache.Ignite.Core.Tests.Cache.Query
var ex = Assert.Throws<NotSupportedException>(() =>
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);
+ Assert.AreEqual("'Contains' clause on compiled query parameter is not supported.", ex.Message);
// check subquery from another cache put in separate variable
var orgIds = orgCache
http://git-wip-us.apache.org/repos/asf/ignite/blob/b6ad6c05/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 f4949d7..735e4f2 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.csproj
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.csproj
@@ -65,11 +65,13 @@
<Compile Include="Impl\CacheQueryParser.cs" />
<Compile Include="Impl\Dml\RemoveAllExpressionNode.cs" />
<Compile Include="Impl\Dml\RemoveAllResultOperator.cs" />
+ <Compile Include="Impl\EnumerableHelper.cs" />
<Compile Include="Impl\ICacheQueryableInternal.cs" />
<Compile Include="Impl\MethodVisitor.cs" />
<Compile Include="Impl\QueryData.cs" />
<Compile Include="Impl\SqlTypes.cs" />
<Compile Include="Impl\ExpressionWalker.cs" />
+ <Compile Include="Impl\JoinInnerSequenceParameterNotNullExpressionVisitor.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/b6ad6c05/modules/platforms/dotnet/Apache.Ignite.Linq/CompiledQuery.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/CompiledQuery.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/CompiledQuery.cs
index e1e1f88..6582c92 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Linq/CompiledQuery.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/CompiledQuery.cs
@@ -50,7 +50,7 @@ namespace Apache.Ignite.Linq
{
IgniteArgumentCheck.NotNull(query, "query");
- var compiledQuery = GetCompiledQuery<T>(query, query.Compile());
+ var compiledQuery = GetCompiledQuery<T>(query);
return () => compiledQuery(new object[0]);
}
@@ -94,7 +94,7 @@ namespace Apache.Ignite.Linq
{
IgniteArgumentCheck.NotNull(query, "query");
- var compiledQuery = GetCompiledQuery<T>(query, query.Compile());
+ var compiledQuery = GetCompiledQuery<T>(query);
return x => compiledQuery(new object[] {x});
}
@@ -111,7 +111,7 @@ namespace Apache.Ignite.Linq
{
IgniteArgumentCheck.NotNull(query, "query");
- var compiledQuery = GetCompiledQuery<T>(query, query.Compile());
+ var compiledQuery = GetCompiledQuery<T>(query);
return (x, y) => compiledQuery(new object[] {x, y});
}
@@ -128,7 +128,7 @@ namespace Apache.Ignite.Linq
{
IgniteArgumentCheck.NotNull(query, "query");
- var compiledQuery = GetCompiledQuery<T>(query, query.Compile());
+ var compiledQuery = GetCompiledQuery<T>(query);
return (x, y, z) => compiledQuery(new object[] {x, y, z});
}
@@ -145,7 +145,7 @@ namespace Apache.Ignite.Linq
{
IgniteArgumentCheck.NotNull(query, "query");
- var compiledQuery = GetCompiledQuery<T>(query, query.Compile());
+ var compiledQuery = GetCompiledQuery<T>(query);
return (x, y, z, a) => compiledQuery(new object[] {x, y, z, a});
}
@@ -162,7 +162,7 @@ namespace Apache.Ignite.Linq
{
IgniteArgumentCheck.NotNull(query, "query");
- var compiledQuery = GetCompiledQuery<T>(query, query.Compile());
+ var compiledQuery = GetCompiledQuery<T>(query);
return (x, y, z, a, b) => compiledQuery(new object[] {x, y, z, a, b});
}
@@ -179,7 +179,7 @@ namespace Apache.Ignite.Linq
{
IgniteArgumentCheck.NotNull(query, "query");
- var compiledQuery = GetCompiledQuery<T>(query, query.Compile());
+ var compiledQuery = GetCompiledQuery<T>(query);
return (x, y, z, a, b, c) => compiledQuery(new object[] {x, y, z, a, b, c});
}
@@ -196,7 +196,7 @@ namespace Apache.Ignite.Linq
{
IgniteArgumentCheck.NotNull(query, "query");
- var compiledQuery = GetCompiledQuery<T>(query, query.Compile());
+ var compiledQuery = GetCompiledQuery<T>(query);
return (x, y, z, a, b, c, d) => compiledQuery(new object[] {x, y, z, a, b, c, d});
}
@@ -213,7 +213,7 @@ namespace Apache.Ignite.Linq
{
IgniteArgumentCheck.NotNull(query, "query");
- var compiledQuery = GetCompiledQuery<T>(query, query.Compile());
+ var compiledQuery = GetCompiledQuery<T>(query);
return (x, y, z, a, b, c, d, e) => compiledQuery(new object[] {x, y, z, a, b, c, d, e});
}
@@ -221,20 +221,21 @@ namespace Apache.Ignite.Linq
/// <summary>
/// Gets the compiled query.
/// </summary>
- private static Func<object[], IQueryCursor<T>> GetCompiledQuery<T>(LambdaExpression expression,
- Delegate queryCaller)
+ private static Func<object[], IQueryCursor<T>> GetCompiledQuery<T>(LambdaExpression expression)
{
Debug.Assert(expression != null);
- Debug.Assert(queryCaller != null);
-
+
// Get default parameter values.
var paramValues = expression.Parameters
.Select(x => x.Type)
.Select(x => x.IsValueType ? Activator.CreateInstance(x) : null)
.ToArray();
- // Invoke the delegate to obtain the cacheQueryable.
- var queryable = queryCaller.DynamicInvoke(paramValues);
+ var transformingxpressionVisitor = new JoinInnerSequenceParameterNotNullExpressionVisitor();
+ var queryCaller = (LambdaExpression)transformingxpressionVisitor.Visit(expression);
+
+ // Compile and invoke the delegate to obtain the cacheQueryable.
+ var queryable = queryCaller.Compile().DynamicInvoke(paramValues);
var cacheQueryable = queryable as ICacheQueryableInternal;
http://git-wip-us.apache.org/repos/asf/ignite/blob/b6ad6c05/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 5fd890d..8902e7c 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/AliasDictionary.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/AliasDictionary.cs
@@ -31,22 +31,29 @@ namespace Apache.Ignite.Linq.Impl
internal class AliasDictionary
{
/** */
- private int _aliasIndex;
+ private int _tableAliasIndex;
/** */
- private Dictionary<IQuerySource, string> _aliases = new Dictionary<IQuerySource, string>();
+ private Dictionary<IQuerySource, string> _tableAliases = new Dictionary<IQuerySource, string>();
/** */
- private readonly Stack<Dictionary<IQuerySource, string>> _stack = new Stack<Dictionary<IQuerySource, string>>();
+ private int _fieldAliasIndex;
+
+ /** */
+ private readonly Dictionary<Expression, string> _fieldAliases = new Dictionary<Expression, string>();
+
+ /** */
+ private readonly Stack<Dictionary<IQuerySource, string>> _stack
+ = new Stack<Dictionary<IQuerySource, string>>();
/// <summary>
/// Pushes current aliases to stack.
/// </summary>
public void Push()
{
- _stack.Push(_aliases);
+ _stack.Push(_tableAliases);
- _aliases = new Dictionary<IQuerySource, string>();
+ _tableAliases = new Dictionary<IQuerySource, string>();
}
/// <summary>
@@ -54,7 +61,7 @@ namespace Apache.Ignite.Linq.Impl
/// </summary>
public void Pop()
{
- _aliases = _stack.Pop();
+ _tableAliases = _stack.Pop();
}
/// <summary>
@@ -67,27 +74,67 @@ namespace Apache.Ignite.Linq.Impl
return GetTableAlias(GetQuerySource(expression));
}
+ /// <summary>
+ /// Gets the table alias.
+ /// </summary>
public string GetTableAlias(IFromClause fromClause)
{
return GetTableAlias(GetQuerySource(fromClause.FromExpression) ?? fromClause);
}
+ /// <summary>
+ /// Gets the table alias.
+ /// </summary>
public string GetTableAlias(JoinClause joinClause)
{
return GetTableAlias(GetQuerySource(joinClause.InnerSequence) ?? joinClause);
}
+ /// <summary>
+ /// Gets the table alias.
+ /// </summary>
private string GetTableAlias(IQuerySource querySource)
{
Debug.Assert(querySource != null);
string alias;
- if (!_aliases.TryGetValue(querySource, out alias))
+ if (!_tableAliases.TryGetValue(querySource, out alias))
+ {
+ alias = "_T" + _tableAliasIndex++;
+
+ _tableAliases[querySource] = alias;
+ }
+
+ return alias;
+ }
+
+ /// <summary>
+ /// Gets the fields alias.
+ /// </summary>
+ public string GetFieldAlias(Expression expression)
+ {
+ Debug.Assert(expression != null);
+
+ var referenceExpression = ExpressionWalker.GetQuerySourceReference(expression);
+
+ return GetFieldAlias(referenceExpression);
+ }
+
+ /// <summary>
+ /// Gets the fields alias.
+ /// </summary>
+ private string GetFieldAlias(QuerySourceReferenceExpression querySource)
+ {
+ Debug.Assert(querySource != null);
+
+ string alias;
+
+ if (!_fieldAliases.TryGetValue(querySource, out alias))
{
- alias = "_T" + _aliasIndex++;
+ alias = "F" + _fieldAliasIndex++;
- _aliases[querySource] = alias;
+ _fieldAliases[querySource] = alias;
}
return alias;
http://git-wip-us.apache.org/repos/asf/ignite/blob/b6ad6c05/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 1b42aad..396458e 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs
@@ -20,7 +20,6 @@ using System.Text;
namespace Apache.Ignite.Linq.Impl
{
- using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
@@ -276,17 +275,29 @@ namespace Apache.Ignite.Linq.Impl
/** <inheritdoc /> */
protected override Expression VisitQuerySourceReference(QuerySourceReferenceExpression expression)
{
- // Count, sum, max, min expect a single field or *
- // In other cases we need both parts of cache entry
- var format = _includeAllFields
- ? "{0}.*, {0}._KEY, {0}._VAL"
- : _useStar
- ? "{0}.*"
- : "{0}._KEY, {0}._VAL";
+ // In some cases of Join clause different handling should be introduced
+ var joinClause = expression.ReferencedQuerySource as JoinClause;
+ if (joinClause != null && ExpressionWalker.GetCacheQueryable(expression, false) == null)
+ {
+ var tableName = Aliases.GetTableAlias(expression);
+ var fieldname = Aliases.GetFieldAlias(expression);
+
+ ResultBuilder.AppendFormat("{0}.{1}", tableName, fieldname);
+ }
+ else
+ {
+ // Count, sum, max, min expect a single field or *
+ // In other cases we need both parts of cache entry
+ var format = _includeAllFields
+ ? "{0}.*, {0}._KEY, {0}._VAL"
+ : _useStar
+ ? "{0}.*"
+ : "{0}._KEY, {0}._VAL";
- var tableName = Aliases.GetTableAlias(expression);
+ var tableName = Aliases.GetTableAlias(expression);
- ResultBuilder.AppendFormat(format, tableName);
+ ResultBuilder.AppendFormat(format, tableName);
+ }
return expression;
}
@@ -563,7 +574,7 @@ namespace Apache.Ignite.Linq.Impl
}
else
{
- var inValues = GetInValues(fromExpression).ToArray();
+ var inValues = ExpressionWalker.EvaluateEnumerableValues(fromExpression).ToArray();
var hasNulls = inValues.Any(o => o == null);
@@ -590,44 +601,6 @@ namespace Apache.Ignite.Linq.Impl
}
/// <summary>
- /// Gets values for IN expression.
- /// </summary>
- private static IEnumerable<object> GetInValues(Expression fromExpression)
- {
- IEnumerable result;
- switch (fromExpression.NodeType)
- {
- case ExpressionType.MemberAccess:
- var memberExpression = (MemberExpression) fromExpression;
- result = ExpressionWalker.EvaluateExpression<IEnumerable>(memberExpression);
- break;
- case ExpressionType.ListInit:
- var listInitExpression = (ListInitExpression) fromExpression;
- result = listInitExpression.Initializers
- .SelectMany(init => init.Arguments)
- .Select(ExpressionWalker.EvaluateExpression<object>);
- break;
- case ExpressionType.NewArrayInit:
- var newArrayExpression = (NewArrayExpression) fromExpression;
- result = newArrayExpression.Expressions
- .Select(ExpressionWalker.EvaluateExpression<object>);
- break;
- case ExpressionType.Parameter:
- // This should happen only when 'IEnumerable.Contains' is called on parameter of compiled query
- throw new NotSupportedException("'Contains' clause coming from compiled query parameter is not supported.");
- default:
- result = Expression.Lambda(fromExpression).Compile().DynamicInvoke() as IEnumerable;
- break;
- }
-
- result = result ?? Enumerable.Empty<object>();
-
- return result
- .Cast<object>()
- .ToArray();
- }
-
- /// <summary>
/// Appends not null parameters using ", " as delimeter.
/// </summary>
private void AppendInParameters(IEnumerable<object> values)
http://git-wip-us.apache.org/repos/asf/ignite/blob/b6ad6c05/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 12b9502..3a3e5fd 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryModelVisitor.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryModelVisitor.cs
@@ -46,6 +46,12 @@ namespace Apache.Ignite.Linq.Impl
/** */
private readonly AliasDictionary _aliases = new AliasDictionary();
+ /** */
+ private static readonly Type DefaultIfEmptyEnumeratorType = new object[0]
+ .DefaultIfEmpty()
+ .GetType()
+ .GetGenericTypeDefinition();
+
/// <summary>
/// Generates the query.
/// </summary>
@@ -466,26 +472,33 @@ namespace Apache.Ignite.Linq.Impl
public override void VisitJoinClause(JoinClause joinClause, QueryModel queryModel, int index)
{
base.VisitJoinClause(joinClause, queryModel, index);
+ var queryable = ExpressionWalker.GetCacheQueryable(joinClause, false);
- var subQuery = joinClause.InnerSequence as SubQueryExpression;
-
- if (subQuery != null)
+ if (queryable != null)
{
- var isOuter = subQuery.QueryModel.ResultOperators.OfType<DefaultIfEmptyResultOperator>().Any();
+ var subQuery = joinClause.InnerSequence as SubQueryExpression;
+
+ if (subQuery != null)
+ {
+ var isOuter = subQuery.QueryModel.ResultOperators.OfType<DefaultIfEmptyResultOperator>().Any();
- _builder.AppendFormat("{0} join (", isOuter ? "left outer" : "inner");
+ _builder.AppendFormat("{0} join (", isOuter ? "left outer" : "inner");
- VisitQueryModel(subQuery.QueryModel, true);
+ VisitQueryModel(subQuery.QueryModel, true);
- var alias = _aliases.GetTableAlias(subQuery.QueryModel.MainFromClause);
- _builder.AppendFormat(") as {0} on (", alias);
+ var alias = _aliases.GetTableAlias(subQuery.QueryModel.MainFromClause);
+ _builder.AppendFormat(") as {0} on (", alias);
+ }
+ else
+ {
+ var tableName = ExpressionWalker.GetTableNameWithSchema(queryable);
+ var alias = _aliases.GetTableAlias(joinClause);
+ _builder.AppendFormat("inner join {0} as {1} on (", tableName, alias);
+ }
}
else
{
- var queryable = ExpressionWalker.GetCacheQueryable(joinClause);
- var tableName = ExpressionWalker.GetTableNameWithSchema(queryable);
- var alias = _aliases.GetTableAlias(joinClause);
- _builder.AppendFormat("inner join {0} as {1} on (", tableName, alias);
+ VisitJoinWithLocalCollectionClause(joinClause);
}
BuildJoinCondition(joinClause.InnerKeySelector, joinClause.OuterKeySelector);
@@ -494,6 +507,65 @@ namespace Apache.Ignite.Linq.Impl
}
/// <summary>
+ /// Visists Join clause in case of join with local collection
+ /// </summary>
+ private void VisitJoinWithLocalCollectionClause(JoinClause joinClause)
+ {
+ var type = joinClause.InnerSequence.Type;
+
+ var itemType = EnumerableHelper.GetIEnumerableItemType(type);
+
+ var sqlTypeName = SqlTypes.GetSqlTypeName(itemType);
+
+ if (string.IsNullOrWhiteSpace(sqlTypeName))
+ {
+ throw new NotSupportedException("Not supported item type for Join with local collection: " + type.Name);
+ }
+
+ var isOuter = false;
+ var sequenceExpression = joinClause.InnerSequence;
+ object values;
+
+ var subQuery = sequenceExpression as SubQueryExpression;
+ if (subQuery != null)
+ {
+ isOuter = subQuery.QueryModel.ResultOperators.OfType<DefaultIfEmptyResultOperator>().Any();
+ sequenceExpression = subQuery.QueryModel.MainFromClause.FromExpression;
+ }
+
+ switch (sequenceExpression.NodeType)
+ {
+ case ExpressionType.Constant:
+ var constantValueType = ((ConstantExpression)sequenceExpression).Value.GetType();
+ if (constantValueType.IsGenericType)
+ {
+ isOuter = DefaultIfEmptyEnumeratorType == constantValueType.GetGenericTypeDefinition();
+ }
+ values = ExpressionWalker.EvaluateEnumerableValues(sequenceExpression);
+ break;
+
+ case ExpressionType.Parameter:
+ values = ExpressionWalker.EvaluateExpression<object>(sequenceExpression);
+ break;
+
+ default:
+ throw new NotSupportedException("Expression not supported for Join with local collection: "
+ + sequenceExpression);
+ }
+
+ var tableAlias = _aliases.GetTableAlias(joinClause);
+ var fieldAlias = _aliases.GetFieldAlias(joinClause.InnerKeySelector);
+
+ _builder.AppendFormat("{0} join table ({1} {2} = ?) {3} on (",
+ isOuter ? "left outer" : "inner",
+ fieldAlias,
+ sqlTypeName,
+ tableAlias);
+
+ Parameters.Add(values);
+ }
+
+ /// <summary>
/// Builds the join condition ('x=y AND foo=bar').
/// </summary>
/// <param name="innerKey">The inner key selector.</param>
http://git-wip-us.apache.org/repos/asf/ignite/blob/b6ad6c05/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/EnumerableHelper.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/EnumerableHelper.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/EnumerableHelper.cs
new file mode 100644
index 0000000..e569481
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/EnumerableHelper.cs
@@ -0,0 +1,59 @@
+/*
+ * 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
+{
+ using System;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Linq;
+
+ /// <summary>
+ /// Contains static methods to work with IEnumerable
+ /// </summary>
+ internal static class EnumerableHelper
+ {
+ /// <summary>
+ /// Gets item type of enumerable
+ /// </summary>
+ public static Type GetIEnumerableItemType(Type type)
+ {
+ Debug.Assert(type != null);
+
+ if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
+ {
+ return type.GetGenericArguments()[0];
+ }
+
+ var implementedIEnumerableType = type.GetInterfaces()
+ .FirstOrDefault(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>));
+
+ if (implementedIEnumerableType != null)
+ {
+ return implementedIEnumerableType.GetGenericArguments()[0];
+ }
+
+ if (type == typeof(IEnumerable))
+ {
+ return typeof(object);
+ }
+
+ throw new NotSupportedException("Type is not IEnumerable: " + type.FullName);
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/b6ad6c05/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/ExpressionWalker.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/ExpressionWalker.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/ExpressionWalker.cs
index f00a13b..36655bc 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/ExpressionWalker.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/ExpressionWalker.cs
@@ -18,6 +18,8 @@
namespace Apache.Ignite.Linq.Impl
{
using System;
+ using System.Collections;
+ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
@@ -38,17 +40,17 @@ namespace Apache.Ignite.Linq.Impl
/// <summary>
/// Gets the cache queryable.
/// </summary>
- public static ICacheQueryableInternal GetCacheQueryable(IFromClause fromClause)
+ public static ICacheQueryableInternal GetCacheQueryable(IFromClause fromClause, bool throwWhenNotFound = true)
{
- return GetCacheQueryable(fromClause.FromExpression);
+ return GetCacheQueryable(fromClause.FromExpression, throwWhenNotFound);
}
/// <summary>
/// Gets the cache queryable.
/// </summary>
- public static ICacheQueryableInternal GetCacheQueryable(JoinClause joinClause)
+ public static ICacheQueryableInternal GetCacheQueryable(JoinClause joinClause, bool throwWhenNotFound = true)
{
- return GetCacheQueryable(joinClause.InnerSequence);
+ return GetCacheQueryable(joinClause.InnerSequence, throwWhenNotFound);
}
/// <summary>
@@ -59,7 +61,7 @@ namespace Apache.Ignite.Linq.Impl
var subQueryExp = expression as SubQueryExpression;
if (subQueryExp != null)
- return GetCacheQueryable(subQueryExp.QueryModel.MainFromClause);
+ return GetCacheQueryable(subQueryExp.QueryModel.MainFromClause, throwWhenNotFound);
var srcRefExp = expression as QuerySourceReferenceExpression;
@@ -68,12 +70,12 @@ namespace Apache.Ignite.Linq.Impl
var fromSource = srcRefExp.ReferencedQuerySource as IFromClause;
if (fromSource != null)
- return GetCacheQueryable(fromSource);
+ return GetCacheQueryable(fromSource, throwWhenNotFound);
var joinSource = srcRefExp.ReferencedQuerySource as JoinClause;
if (joinSource != null)
- return GetCacheQueryable(joinSource);
+ return GetCacheQueryable(joinSource, throwWhenNotFound);
throw new NotSupportedException("Unexpected query source: " + srcRefExp.ReferencedQuerySource);
}
@@ -114,6 +116,38 @@ namespace Apache.Ignite.Linq.Impl
}
/// <summary>
+ /// Tries to find QuerySourceReferenceExpression
+ /// </summary>
+ public static QuerySourceReferenceExpression GetQuerySourceReference(Expression expression,
+ bool throwWhenNotFound = true)
+ {
+ var reference = expression as QuerySourceReferenceExpression;
+ if (reference != null)
+ {
+ return reference;
+ }
+
+ var unary = expression as UnaryExpression;
+ if (unary != null)
+ {
+ return GetQuerySourceReference(unary.Operand, false);
+ }
+
+ var binary = expression as BinaryExpression;
+ if (binary != null)
+ {
+ return GetQuerySourceReference(binary.Left, false) ?? GetQuerySourceReference(binary.Right, false);
+ }
+
+ if (throwWhenNotFound)
+ {
+ throw new NotSupportedException("Unexpected query source: " + expression);
+ }
+
+ return null;
+ }
+
+ /// <summary>
/// Evaluates the expression.
/// </summary>
public static T EvaluateExpression<T>(Expression expr)
@@ -151,6 +185,44 @@ namespace Apache.Ignite.Linq.Impl
}
/// <summary>
+ /// Gets the values from IEnumerable expression
+ /// </summary>
+ public static IEnumerable<object> EvaluateEnumerableValues(Expression fromExpression)
+ {
+ IEnumerable result;
+ switch (fromExpression.NodeType)
+ {
+ case ExpressionType.MemberAccess:
+ var memberExpression = (MemberExpression)fromExpression;
+ result = EvaluateExpression<IEnumerable>(memberExpression);
+ break;
+ case ExpressionType.ListInit:
+ var listInitExpression = (ListInitExpression)fromExpression;
+ result = listInitExpression.Initializers
+ .SelectMany(init => init.Arguments)
+ .Select(EvaluateExpression<object>);
+ break;
+ case ExpressionType.NewArrayInit:
+ var newArrayExpression = (NewArrayExpression)fromExpression;
+ result = newArrayExpression.Expressions
+ .Select(EvaluateExpression<object>);
+ break;
+ case ExpressionType.Parameter:
+ // This should happen only when 'IEnumerable.Contains' is called on parameter of compiled query
+ throw new NotSupportedException("'Contains' clause on compiled query parameter is not supported.");
+ default:
+ result = Expression.Lambda(fromExpression).Compile().DynamicInvoke() as IEnumerable;
+ break;
+ }
+
+ result = result ?? Enumerable.Empty<object>();
+
+ return result
+ .Cast<object>()
+ .ToArray();
+ }
+
+ /// <summary>
/// Compiles the member reader.
/// </summary>
private static Func<object, object> CompileMemberReader(MemberExpression memberExpr)
http://git-wip-us.apache.org/repos/asf/ignite/blob/b6ad6c05/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/JoinInnerSequenceParameterNotNullExpressionVisitor.cs
----------------------------------------------------------------------
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/JoinInnerSequenceParameterNotNullExpressionVisitor.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/JoinInnerSequenceParameterNotNullExpressionVisitor.cs
new file mode 100644
index 0000000..fa8ed1f
--- /dev/null
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/JoinInnerSequenceParameterNotNullExpressionVisitor.cs
@@ -0,0 +1,72 @@
+/*
+ * 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
+{
+ using System;
+ using System.Collections;
+ using System.Linq;
+ using System.Linq.Expressions;
+ using System.Reflection;
+ using Remotion.Linq.Parsing;
+
+ /// <summary>
+ /// Transforms JoinClause with parameterised inner sequence to .Join(innerSequence ?? new T[0] ...
+ /// </summary>
+ internal class JoinInnerSequenceParameterNotNullExpressionVisitor : RelinqExpressionVisitor
+ {
+ /** */
+ private static readonly MethodInfo[] JoinMethods = typeof(Queryable).GetMethods()
+ .Where(info => info.Name == "Join")
+ .ToArray();
+
+ /** */
+ private static readonly Type EnumerableType = typeof(IEnumerable);
+
+ /** */
+ private bool _inJoin;
+
+ /** <inheritdoc /> */
+ protected override Expression VisitMethodCall(MethodCallExpression node)
+ {
+ if (node.Method.IsGenericMethod)
+ {
+ var genericMethodDefinition = node.Method.GetGenericMethodDefinition();
+
+ _inJoin = JoinMethods.Any(mi => mi == genericMethodDefinition);
+ }
+
+ var result = base.VisitMethodCall(node);
+
+ _inJoin = false;
+
+ return result;
+ }
+
+ /** <inheritdoc /> */
+ protected override Expression VisitParameter(ParameterExpression node)
+ {
+ if (_inJoin && EnumerableType.IsAssignableFrom(node.Type))
+ {
+ var itemType = EnumerableHelper.GetIEnumerableItemType(node.Type);
+ return Expression.Coalesce(node, Expression.NewArrayBounds(itemType, Expression.Constant(0)));
+ }
+
+ return node;
+ }
+ }
+}
\ No newline at end of file