ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sboi...@apache.org
Subject [2/6] ignite git commit: IGNITE-4636 .NET: Support local collection joins in LINQ
Date Wed, 14 Jun 2017 18:31:45 GMT
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/ignite-5272
Commit: b6ad6c055a1870945222b4b6a49ca7b670a829fe
Parents: 6283bd8
Author: Sergey Stronchinskiy <gurustronpublic@gmail.com>
Authored: Wed Jun 14 18:24:07 2017 +0300
Committer: Pavel Tupitsyn <ptupitsyn@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


Mime
View raw message