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 2019/08/06 11:17:10 UTC

[ignite] branch master updated: IGNITE-11985 .NET: Fix LINQ nulls handling

This is an automated email from the ASF dual-hosted git repository.

ptupitsyn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new b3e04fd  IGNITE-11985 .NET: Fix LINQ nulls handling
b3e04fd is described below

commit b3e04fdad6e8a1cad3666a8ca58e05f0112fe4fb
Author: Pavel Tupitsyn <pt...@apache.org>
AuthorDate: Tue Aug 6 14:16:52 2019 +0300

    IGNITE-11985 .NET: Fix LINQ nulls handling
    
    Use SQL `IS [NOT] DISTINCT FROM` instead of `=` and `<>` to fix null comparisons in LINQ
---
 .../Query/Linq/CacheLinqTest.CompiledQuery.cs      | 65 +++++++++++++++++++++-
 .../Cache/Query/Linq/CacheLinqTest.Functions.cs    |  7 ++-
 .../Impl/CacheQueryExpressionVisitor.cs            | 22 ++------
 3 files changed, 72 insertions(+), 22 deletions(-)

diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.CompiledQuery.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.CompiledQuery.cs
index 1cd377e..75532be 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.CompiledQuery.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.CompiledQuery.cs
@@ -26,7 +26,10 @@
 // ReSharper disable UnusedMemberInSuper.Global
 namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
 {
+    using System;
     using System.Linq;
+    using Apache.Ignite.Core.Cache;
+    using Apache.Ignite.Core.Cache.Query;
     using Apache.Ignite.Linq;
     using NUnit.Framework;
 
@@ -211,5 +214,65 @@ namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
 
             Assert.AreEqual(17, compiled2(18, 16, "ers", 13, 17).Single().Key);
         }
+
+        [Test]
+        public void TestCompiledQueryStringEquals()
+        {
+            var persons = GetPersonCache().AsCacheQueryable();
+            var qry0 = CompiledQuery.Compile((string empName) => persons.Where(x => x.Value.Name == empName));
+            Assert.AreEqual(1, qry0(" Person_1  ").Count());
+            Assert.AreEqual(0, qry0(null).Count());
+        }
+
+        [Test]
+        public void TestCompiledQueryStringNotEquals()
+        {
+            var persons = GetPersonCache().AsCacheQueryable();
+            var qry0 = CompiledQuery.Compile((string empName) => persons.Where(x => x.Value.Name != empName));
+            Assert.AreEqual(PersonCount - 1, qry0(" Person_1  ").Count());
+            Assert.AreEqual(PersonCount, qry0(null).Count());
+        }
+
+        [Test]
+        public void TestCompiledQueryStringEqualsNull()
+        {
+            var roles = GetRoleCache().AsCacheQueryable();
+
+            var nullNameRoles = CompiledQuery.Compile((string roleName) => roles.Where(x => x.Value.Name == roleName));
+            Assert.AreEqual(1, nullNameRoles(null).Count());
+        }
+
+        [Test]
+        public void TestCompiledQueryStringNotEqualsNull()
+        {
+            var roles = GetRoleCache().AsCacheQueryable();
+
+            var nonNullNameRoles = CompiledQuery.Compile((string roleName) => roles.Where(x => x.Value.Name != roleName));
+            Assert.AreEqual(RoleCount - 1, nonNullNameRoles(null).Count());
+        }
+
+        [Test]
+        public void TestCompiledQueryStringEqualsFreeForm()
+        {
+            var persons = GetPersonCache().AsCacheQueryable().Where(emp => emp.Value.Name == "unused");
+            var compiledQuery = CompiledQuery.Compile(persons);
+
+            Func<string, IQueryCursor<ICacheEntry<int, Person>>> parametrizedCompiledQuery =
+                empName => compiledQuery(empName);
+
+            Assert.AreEqual(1, parametrizedCompiledQuery(" Person_0  ").Count());
+        }
+
+        [Test]
+        public void TestCompiledQueryStringNotEqualsFreeForm()
+        {
+            var persons = GetPersonCache().AsCacheQueryable().Where(emp => emp.Value.Name != "unused");
+            var compiledQuery = CompiledQuery.Compile(persons);
+
+            Func<string, IQueryCursor<ICacheEntry<int, Person>>> parametrizedCompiledQuery =
+                empName => compiledQuery(empName);
+
+            Assert.AreEqual(PersonCount - 1, parametrizedCompiledQuery(" Person_0  ").Count());
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Functions.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Functions.cs
index a6632d9..dd6b2ce 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Functions.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Functions.cs
@@ -55,8 +55,9 @@ namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
             Assert.AreEqual(key, cache.Where(x => -x.Key > -key).ToArray().Length);
 
             Assert.AreEqual(1, GetRoleCache().AsCacheQueryable().Where(x => x.Key.Foo < 2).ToArray().Length);
-            Assert.AreEqual(2, GetRoleCache().AsCacheQueryable().Where(x => x.Key.Bar > 2 && x.Value.Name != "11")
-                .ToArray().Length);
+            var cacheEntries = GetRoleCache().AsCacheQueryable().Where(x => x.Key.Bar > 2 && x.Value.Name != "11")
+                .ToArray();
+            Assert.AreEqual(3, cacheEntries.Length);
         }
 
         /// <summary>
@@ -256,4 +257,4 @@ namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
             Assert.AreEqual(expectedIds, actualIds);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs
index c5c887f..cc14260 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs
@@ -182,31 +182,17 @@ namespace Apache.Ignite.Linq.Impl
             {
                 case ExpressionType.Equal:
                 {
-                    var rightConst = expression.Right as ConstantExpression;
+                    // Use `IS [NOT] DISTINCT FROM` for correct null comparison semantics.
+                    // E.g. when user says `.Where(x => x == null)`, it should work, but with `=` it does not.
+                    ResultBuilder.Append(" IS NOT DISTINCT FROM ");
 
-                    if (rightConst != null && rightConst.Value == null)
-                    {
-                        // Special case for nulls, since "= null" does not work in SQL
-                        ResultBuilder.Append(" is null)");
-                        return expression;
-                    }
-
-                    ResultBuilder.Append(" = ");
                     break;
                 }
 
                 case ExpressionType.NotEqual:
                 {
-                    var rightConst = expression.Right as ConstantExpression;
-
-                    if (rightConst != null && rightConst.Value == null)
-                    {
-                        // Special case for nulls, since "<> null" does not work in SQL
-                        ResultBuilder.Append(" is not null)");
-                        return expression;
-                    }
+                    ResultBuilder.Append(" IS DISTINCT FROM ");
 
-                    ResultBuilder.Append(" <> ");
                     break;
                 }