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 2022/12/01 19:29:12 UTC

[ignite-3] branch main updated: IGNITE-18131 .NET: LINQ: Improve Distinct support (#1401)

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

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


The following commit(s) were added to refs/heads/main by this push:
     new e891461401 IGNITE-18131 .NET: LINQ: Improve Distinct support (#1401)
e891461401 is described below

commit e89146140165fe782eb2f248267e1e0ba13514fa
Author: Pavel Tupitsyn <pt...@apache.org>
AuthorDate: Thu Dec 1 22:29:07 2022 +0300

    IGNITE-18131 .NET: LINQ: Improve Distinct support (#1401)
    
    * Use column alias when projecting a complex expression into a new type to support some `.Distinct()` use cases.
    * Add more tests.
---
 .../Linq/LinqSqlGenerationTests.KvView.cs          |  2 +-
 .../Linq/LinqSqlGenerationTests.cs                 | 13 ++--
 .../Linq/LinqTests.Aggregate.cs                    |  2 +-
 .../Apache.Ignite.Tests/Linq/LinqTests.Cast.cs     |  4 +-
 .../Apache.Ignite.Tests/Linq/LinqTests.GroupBy.cs  |  6 +-
 .../Linq/LinqTests.UnionIntersectExcept.cs         |  2 +-
 .../dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs   | 85 ++++++++++++++++++++++
 .../Internal/Linq/IgniteQueryExpressionVisitor.cs  | 53 +++++++-------
 .../Internal/Linq/IgniteQueryModelVisitor.cs       |  2 +-
 9 files changed, 126 insertions(+), 43 deletions(-)

diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqSqlGenerationTests.KvView.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqSqlGenerationTests.KvView.cs
index 4f5f800623..aba2deb438 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqSqlGenerationTests.KvView.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqSqlGenerationTests.KvView.cs
@@ -42,7 +42,7 @@ public partial class LinqSqlGenerationTests
     [Test]
     public void TestSelectTwoColumnsKv() =>
         AssertSqlKv(
-            "select (_T0.KEY + ?), _T0.VAL from PUBLIC.tbl1 as _T0",
+            "select (_T0.KEY + ?) as KEY, _T0.VAL from PUBLIC.tbl1 as _T0",
             q => q.Select(x => new { Key = x.Key.Key + 1, x.Value.Val }).ToList());
 
     [Test]
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqSqlGenerationTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqSqlGenerationTests.cs
index 0c178937d3..4d63da2b12 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqSqlGenerationTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqSqlGenerationTests.cs
@@ -101,7 +101,7 @@ public partial class LinqSqlGenerationTests
     [Test]
     public void TestSelectOrderByOffsetLimit() =>
         AssertSql(
-            "select _T0.KEY, _T0.VAL, (_T0.KEY + ?) " +
+            "select _T0.KEY, _T0.VAL, (_T0.KEY + ?) as KEY2 " +
             "from PUBLIC.tbl1 as _T0 " +
             "order by ((_T0.KEY + ?)) asc, (_T0.VAL) desc " +
             "limit ? offset ?",
@@ -149,10 +149,9 @@ public partial class LinqSqlGenerationTests
     }
 
     [Test]
-    [Ignore("IGNITE-18131 Distinct support")]
     public void TestSelectOrderDistinct() =>
         AssertSql(
-            "select distinct _T0.KEY, (_T0.KEY + ?) from PUBLIC.tbl1 as _T0 order by ((_T0.KEY + ?)) asc",
+            "select * from (select distinct _T0.KEY, (_T0.KEY + ?) as KEY2 from PUBLIC.tbl1 as _T0) as _T1 order by (_T1.KEY2) asc",
             q => q.Select(x => new { x.Key, Key2 = x.Key + 1})
                 .Distinct()
                 .OrderBy(x => x.Key2)
@@ -246,8 +245,8 @@ public partial class LinqSqlGenerationTests
     [Test]
     public void TestUnion() =>
         AssertSql(
-            "select (_T0.KEY + ?), _T0.VAL from PUBLIC.tbl1 as _T0 " +
-            "union (select (_T1.KEY + ?), _T1.VAL from PUBLIC.tbl1 as _T1)",
+            "select (_T0.KEY + ?) as KEY, _T0.VAL from PUBLIC.tbl1 as _T0 " +
+            "union (select (_T1.KEY + ?) as KEY, _T1.VAL from PUBLIC.tbl1 as _T1)",
             q => q.Select(x => new { Key = x.Key + 1, x.Val })
                 .Union(q.Select(x => new { Key = x.Key + 100, x.Val }))
                 .ToList());
@@ -255,8 +254,8 @@ public partial class LinqSqlGenerationTests
     [Test]
     public void TestIntersect() =>
         AssertSql(
-            "select (_T0.KEY + ?), concat(_T0.VAL, ?) from PUBLIC.tbl1 as _T0 " +
-            "intersect (select (_T1.KEY + ?), concat(_T1.VAL, ?) from PUBLIC.tbl1 as _T1)",
+            "select (_T0.KEY + ?) as KEY, concat(_T0.VAL, ?) as VAL from PUBLIC.tbl1 as _T0 " +
+            "intersect (select (_T1.KEY + ?) as KEY, concat(_T1.VAL, ?) as VAL from PUBLIC.tbl1 as _T1)",
             q => q.Select(x => new { Key = x.Key + 1, Val = x.Val + "_" })
                 .Intersect(q.Select(x => new { Key = x.Key + 100, Val = x.Val + "!" }))
                 .ToList());
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Aggregate.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Aggregate.cs
index e186ceac80..4912bfdd93 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Aggregate.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Aggregate.cs
@@ -110,7 +110,7 @@ public partial class LinqTests
         Assert.AreEqual(2, res[2].Max);
 
         StringAssert.Contains(
-            "select _T0.KEY, count(*), sum(_T0.KEY), avg(_T0.KEY), min(_T0.KEY), max(_T0.KEY) " +
+            "select _T0.KEY, count(*) as COUNT, sum(_T0.KEY) as SUM, avg(_T0.KEY) as AVG, min(_T0.KEY) as MIN, max(_T0.KEY) as MAX " +
             "from PUBLIC.TBL_INT32 as _T0 " +
             "group by (_T0.KEY) " +
             "order by (_T0.KEY) asc",
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Cast.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Cast.cs
index f539304012..63a2078995 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Cast.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.Cast.cs
@@ -49,8 +49,8 @@ public partial class LinqTests
         Assert.AreEqual(900d / 2000, res[0].Double);
 
         StringAssert.Contains(
-            "select cast(_T0.VAL as tinyint), cast(_T0.VAL as smallint), cast(_T0.VAL as bigint), " +
-            "(cast(_T0.VAL as real) / ?), (cast(_T0.VAL as double) / ?) " +
+            "select cast(_T0.VAL as tinyint) as BYTE, cast(_T0.VAL as smallint) as SHORT, cast(_T0.VAL as bigint) as LONG, " +
+            "(cast(_T0.VAL as real) / ?) as FLOAT, (cast(_T0.VAL as double) / ?) as DOUBLE " +
             "from PUBLIC.TBL_INT32 as _T0 " +
             "order by (cast(_T0.VAL as bigint)) desc",
             query.ToString());
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.GroupBy.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.GroupBy.cs
index 7ae5615831..16f002c35e 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.GroupBy.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.GroupBy.cs
@@ -83,7 +83,7 @@ public partial class LinqTests
         Assert.AreEqual(4.0d, res[1].Avg);
 
         StringAssert.Contains(
-            "select _T0.VAL, count(*), sum(cast(_T0.KEY as int)), avg(cast(_T0.KEY as int)) " +
+            "select _T0.VAL, count(*) as COUNT, sum(cast(_T0.KEY as int)) as SUM, avg(cast(_T0.KEY as int)) as AVG " +
             "from PUBLIC.TBL_INT8 as _T0 " +
             "group by (_T0.VAL) " +
             "order by (_T0.VAL) asc",
@@ -140,7 +140,7 @@ public partial class LinqTests
         Assert.AreEqual(10, res.Count);
 
         StringAssert.Contains(
-            "select _T0.VAL, count(*) " +
+            "select _T0.VAL, count(*) as COUNT " +
             "from PUBLIC.TBL1 as _T1 " +
             "inner join PUBLIC.TBL_INT32 as _T0 on (cast(_T0.KEY as bigint) = _T1.KEY) " +
             "group by (_T0.VAL) " +
@@ -179,7 +179,7 @@ public partial class LinqTests
         Assert.AreEqual(900, res[0].MaxPrice);
 
         StringAssert.Contains(
-            "select _T0.VAL, max(_T1.VAL) " +
+            "select _T0.VAL, max(_T1.VAL) as MAXPRICE " +
             "from PUBLIC.TBL1 as _T0 " +
             "inner join PUBLIC.TBL_INT32 as _T1 on (cast(_T1.KEY as bigint) = _T0.KEY) " +
             "group by (_T0.VAL) " +
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.UnionIntersectExcept.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.UnionIntersectExcept.cs
index f6bc7a9be2..ad1772cebd 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.UnionIntersectExcept.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.UnionIntersectExcept.cs
@@ -89,7 +89,7 @@ public partial class LinqTests
         CollectionAssert.AreEquivalent(new[] { 4, 9 }, res.Select(x => x.Key));
 
         StringAssert.Contains(
-            "select cast(_T0.KEY as int) " +
+            "select cast(_T0.KEY as int) as KEY " +
             "from PUBLIC.TBL_INT8 as _T0 " +
             "where (cast(_T0.KEY as int) > ?) " +
             "union (select _T1.KEY from PUBLIC.TBL_INT32 as _T1 where ((_T1.KEY > ?) and (_T1.KEY < ?)))",
diff --git a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs
index 379e5bcddc..8176c3995f 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Tests/Linq/LinqTests.cs
@@ -288,6 +288,7 @@ public partial class LinqTests : IgniteTestsBase
     }
 
     [Test]
+    [Ignore("IGNITE-18311")]
     public void TestOrderBySkipTakeBeforeSelect()
     {
         var query = PocoView.AsQueryable()
@@ -327,6 +328,90 @@ public partial class LinqTests : IgniteTestsBase
             query.ToString());
     }
 
+    [Test]
+    public void TestDistinctOneField()
+    {
+        var query = PocoByteView.AsQueryable()
+            .Select(x => x.Val)
+            .Distinct();
+
+        List<sbyte> res = query.ToList();
+
+        CollectionAssert.AreEquivalent(new[] { 0, 1, 2, 3 }, res);
+
+        StringAssert.Contains("select distinct _T0.VAL from PUBLIC.TBL_INT8 as _T0", query.ToString());
+    }
+
+    [Test]
+    public void TestDistinctEntireObject()
+    {
+        var query = PocoByteView.AsQueryable()
+            .Distinct();
+
+        List<PocoByte> res = query.ToList();
+
+        Assert.AreEqual(10, res.Count);
+
+        StringAssert.Contains("select distinct _T0.KEY, _T0.VAL from PUBLIC.TBL_INT8 as _T0", query.ToString());
+    }
+
+    [Test]
+    public void TestDistinctProjection()
+    {
+        var query = PocoByteView.AsQueryable()
+            .Select(x => new { Id = x.Val + 10, V = x.Val })
+            .Distinct();
+
+        var res = query.ToList();
+
+        Assert.AreEqual(4, res.Count);
+
+        StringAssert.Contains(
+            "select distinct (cast(_T0.VAL as int) + ?) as ID, _T0.VAL " +
+            "from PUBLIC.TBL_INT8 as _T0",
+            query.ToString());
+    }
+
+    [Test]
+    public void TestDistinctAfterOrderBy()
+    {
+        var query = PocoByteView.AsQueryable()
+            .Select(x => new { Id = x.Val + 10, V = x.Val })
+            .OrderByDescending(x => x.V)
+            .Distinct();
+
+        var res = query.ToList();
+
+        Assert.AreEqual(4, res.Count);
+        Assert.AreEqual(13, res[0].Id);
+
+        StringAssert.Contains(
+            "select distinct (cast(_T0.VAL as int) + ?) as ID, _T0.VAL " +
+            "from PUBLIC.TBL_INT8 as _T0 " +
+            "order by (_T0.VAL) desc",
+            query.ToString());
+    }
+
+    [Test]
+    public void TestDistinctBeforeOrderBy()
+    {
+        var query = PocoByteView.AsQueryable()
+            .Select(x => new { Id = x.Val + 10, V = x.Val })
+            .Distinct()
+            .OrderByDescending(x => x.Id);
+
+        var res = query.ToList();
+
+        Assert.AreEqual(4, res.Count);
+        Assert.AreEqual(13, res[0].Id);
+
+        StringAssert.Contains(
+            "select * from " +
+            "(select distinct (cast(_T0.VAL as int) + ?) as ID, _T0.VAL from PUBLIC.TBL_INT8 as _T0) as _T1 " +
+            "order by (_T1.ID) desc",
+            query.ToString());
+    }
+
     [Test]
     public void TestCustomColumnNameMapping()
     {
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryExpressionVisitor.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryExpressionVisitor.cs
index 1e033cedda..1d2d413984 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryExpressionVisitor.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryExpressionVisitor.cs
@@ -302,7 +302,32 @@ internal sealed class IgniteQueryExpressionVisitor : ThrowingExpressionVisitor
     /** <inheritdoc /> */
     protected override Expression VisitNew(NewExpression expression)
     {
-        VisitArguments(expression.Arguments);
+        var first = true;
+
+        for (var i = 0; i < expression.Arguments.Count; i++)
+        {
+            var arg = expression.Arguments[i];
+            if (!first)
+            {
+                if (_useStar)
+                {
+                    throw new NotSupportedException("Aggregate functions do not support multiple fields");
+                }
+
+                ResultBuilder.TrimEnd().Append(", ");
+            }
+
+            first = false;
+
+            Visit(arg);
+
+            // When projection uses projection comes from a complex expression, append an alias.
+            var param = expression.Members?[i];
+            if (param != null && arg is not MemberExpression)
+            {
+                ResultBuilder.AppendWithSpace("as ").Append(param.Name.ToUpperInvariant());
+            }
+        }
 
         return expression;
     }
@@ -588,32 +613,6 @@ internal sealed class IgniteQueryExpressionVisitor : ThrowingExpressionVisitor
         }
     }
 
-    /// <summary>
-    /// Visits multiple arguments.
-    /// </summary>
-    /// <param name="arguments">The arguments.</param>
-    private void VisitArguments(IEnumerable<Expression> arguments)
-    {
-        var first = true;
-
-        foreach (var e in arguments)
-        {
-            if (!first)
-            {
-                if (_useStar)
-                {
-                    throw new NotSupportedException("Aggregate functions do not support multiple fields");
-                }
-
-                ResultBuilder.TrimEnd().Append(", ");
-            }
-
-            first = false;
-
-            Visit(e);
-        }
-    }
-
     /// <summary>
     /// Visits the group by member.
     /// </summary>
diff --git a/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryModelVisitor.cs b/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryModelVisitor.cs
index d16d02ab0f..c685d0bacc 100644
--- a/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryModelVisitor.cs
+++ b/modules/platforms/dotnet/Apache.Ignite/Internal/Linq/IgniteQueryModelVisitor.cs
@@ -186,7 +186,7 @@ internal sealed class IgniteQueryModelVisitor : QueryModelVisitorBase
         {
             _builder.Append("from (");
 
-            VisitQueryModel(subQuery.QueryModel);
+            VisitQueryModel(subQuery.QueryModel, includeAllFields: true);
 
             _builder.TrimEnd()
                 .Append(") as ")