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 2021/02/02 09:48:35 UTC

[ignite] branch master updated: IGNITE-14064 .NET: Fix SQL table name for generic query types

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 8ab3211  IGNITE-14064 .NET: Fix SQL table name for generic query types
8ab3211 is described below

commit 8ab32115ab98e1f814933fc6de2df729c1a356a6
Author: Pavel Tupitsyn <pt...@apache.org>
AuthorDate: Tue Feb 2 12:48:13 2021 +0300

    IGNITE-14064 .NET: Fix SQL table name for generic query types
    
    * Fix `BinaryNameMapper.simpleName` to strip namespaces from generic types
    * Fix `QueryUtils.typeName` to strip generic part
---
 .../ignite/binary/BinaryBasicNameMapper.java       |  24 ++++
 .../internal/processors/query/QueryUtils.java      |   5 +
 .../binary/BinaryBasicNameMapperSelfTest.java      |  21 +++
 .../Query/CacheQueriesCodeConfigurationTest.cs     |  17 +--
 .../Cache/Query/Linq/CacheLinqTest.Misc.cs         | 148 +++++++++++++++++++++
 5 files changed, 203 insertions(+), 12 deletions(-)

diff --git a/modules/core/src/main/java/org/apache/ignite/binary/BinaryBasicNameMapper.java b/modules/core/src/main/java/org/apache/ignite/binary/BinaryBasicNameMapper.java
index 697108f..cf99e95 100644
--- a/modules/core/src/main/java/org/apache/ignite/binary/BinaryBasicNameMapper.java
+++ b/modules/core/src/main/java/org/apache/ignite/binary/BinaryBasicNameMapper.java
@@ -87,6 +87,8 @@ public class BinaryBasicNameMapper implements BinaryNameMapper {
     private static String simpleName(String clsName) {
         assert clsName != null;
 
+        clsName = simplifyDotNetGenerics(clsName);
+
         int idx = clsName.lastIndexOf('$');
 
         if (idx == clsName.length() - 1)
@@ -119,6 +121,28 @@ public class BinaryBasicNameMapper implements BinaryNameMapper {
         return idx >= 0 ? clsName.substring(idx + 1) : clsName;
     }
 
+    /**
+     * Converts .NET generic type arguments to a simple form (without namespaces and outer classes classes).
+     *
+     * @param clsName Class name.
+     * @return Simplified class name.
+     */
+    private static String simplifyDotNetGenerics(String clsName) {
+        // .NET generic part starts with [[ (not valid for Java class name). Clean up every generic part recursively.
+        // Example: Foo.Bar`1[[Baz.Qux`2[[System.String],[System.Int32]]]]
+        int genericIdx = clsName.indexOf("[[");
+
+        if (genericIdx > 0)
+            clsName = clsName.substring(0, genericIdx + 2) + simpleName(clsName.substring(genericIdx + 2));
+
+        genericIdx = clsName.indexOf("],[", genericIdx);
+
+        if (genericIdx > 0)
+            clsName = clsName.substring(0, genericIdx + 3) + simpleName(clsName.substring(genericIdx + 3));
+
+        return clsName;
+    }
+
     /** {@inheritDoc} */
     @Override public boolean equals(Object o) {
         if (this == o)
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryUtils.java
index c561a5e..bff1ae2 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryUtils.java
@@ -1115,6 +1115,11 @@ public class QueryUtils {
      * @return Type name.
      */
     public static String typeName(String clsName) {
+        int genericStart = clsName.indexOf('`');  // .NET generic, not valid for Java class name.
+
+        if (genericStart >= 0)
+            clsName = clsName.substring(0, genericStart);
+
         int pkgEnd = clsName.lastIndexOf('.');
 
         if (pkgEnd >= 0 && pkgEnd < clsName.length() - 1)
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryBasicNameMapperSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryBasicNameMapperSelfTest.java
index cd4cff1..7237cca 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryBasicNameMapperSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryBasicNameMapperSelfTest.java
@@ -42,6 +42,27 @@ public class BinaryBasicNameMapperSelfTest extends GridCommonAbstractTest {
      * @throws Exception If failed.
      */
     @Test
+    public void testSimpleNameDotNet() throws Exception {
+        BinaryBasicNameMapper mapper = new BinaryBasicNameMapper(true);
+
+        assertEquals("Baz", mapper.typeName("Foo.Bar.Baz"));
+
+        assertEquals("Bar`1[[Qux]]", mapper.typeName("Foo.Bar`1[[Baz.Qux]]"));
+
+        assertEquals("List`1[[Int32[]]]",
+                mapper.typeName("System.Collections.Generic.List`1[[System.Int32[]]]"));
+
+        assertEquals("Bar`1[[Qux`2[[String],[Int32]]]]",
+                mapper.typeName("Foo.Bar`1[[Baz.Qux`2[[System.String],[System.Int32]]]]"));
+
+        assertEquals("Bar`1[[Qux`2[[C],[Int32]]]]",
+                mapper.typeName("Foo.Outer+Bar`1[[Baz.Outer2+Qux`2[[A.B+C],[System.Int32]]]]"));
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @Test
     public void testFullName() throws Exception {
         BinaryBasicNameMapper mapper = new BinaryBasicNameMapper(false);
 
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheQueriesCodeConfigurationTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheQueriesCodeConfigurationTest.cs
index 261071a..a80526d 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheQueriesCodeConfigurationTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheQueriesCodeConfigurationTest.cs
@@ -270,12 +270,7 @@ namespace Apache.Ignite.Core.Tests.Cache.Query
             var queryEntity = cache.GetConfiguration().QueryEntities.Single();
             Assert.AreEqual(expectedTypeName, queryEntity.ValueTypeName);
 
-            var tableName = cache.Query(new SqlFieldsQuery(
-                "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=?", cache.Name))
-                .Single().Single(); // The table name is weird, see IGNITE-14064.
-
-            var sqlRes = cache.Query(new SqlFieldsQuery(string.Format("SELECT Foo, Bar from \"{0}\"", tableName)))
-                .Single();
+            var sqlRes = cache.Query(new SqlFieldsQuery("SELECT Foo, Bar from GENERICTEST2")).Single();
 
             Assert.AreEqual(key.Foo, sqlRes[0]);
             Assert.AreEqual(value.Bar, sqlRes[1]);
@@ -298,14 +293,12 @@ namespace Apache.Ignite.Core.Tests.Cache.Query
             var value = new GenericTest<GenericTest2<string>>(new GenericTest2<string>("foobar"));
             cache[1] = value;
 
-            var tableName = cache.Query(new SqlFieldsQuery(
-                    "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=?", cache.Name))
-                .Single().Single(); // The table name is weird, see IGNITE-14064.
-
-            var sqlRes = cache.Query(new SqlFieldsQuery(string.Format("SELECT Bar from \"{0}\"", tableName)))
-                .Single().Single();
+            var sqlRes = cache.Query(new SqlFieldsQuery("SELECT Bar from GENERICTEST")).Single().Single();
 
             Assert.AreEqual(value.Foo.Bar, sqlRes);
+
+            var valTypeName = value.GetType().FullName;
+            Assert.IsNotNull(valTypeName);
         }
 
         /// <summary>
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Misc.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Misc.cs
index 15bcfa6..bc67ea7 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Misc.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/Linq/CacheLinqTest.Misc.cs
@@ -380,5 +380,153 @@ namespace Apache.Ignite.Core.Tests.Cache.Query.Linq
                 .Select(x => selector(x))
                 .FirstOrDefault());
         }
+
+        /// <summary>
+        /// Tests queries when cache key/val types are generic.
+        /// </summary>
+        [Test]
+        public void TestGenericCacheTypes()
+        {
+            var cfg = new CacheConfiguration(TestUtils.TestName)
+            {
+                QueryEntities = new[] {new QueryEntity(typeof(GenericTest<int>), typeof(GenericTest2<string>))}
+            };
+
+            var cache = Ignition.GetIgnite().GetOrCreateCache<GenericTest<int>, GenericTest2<string>>(cfg);
+            var key = new GenericTest<int>(1);
+            var value = new GenericTest2<string>("foo");
+            cache[key] = value;
+
+            var query = cache.AsCacheQueryable()
+                .Where(x => x.Key.Foo == key.Foo && x.Value.Bar == value.Bar)
+                .Select(x => x.Value.Bar);
+
+            var sql = query.ToCacheQueryable().GetFieldsQuery().Sql;
+            var res = query.ToList();
+
+            Assert.AreEqual(1, res.Count);
+            Assert.AreEqual(value.Bar, res[0]);
+
+            var expectedSql = string.Format("select _T0.BAR from \"{0}\".GENERICTEST2 as", cache.Name);
+            StringAssert.StartsWith(expectedSql, sql);
+        }
+
+        /// <summary>
+        /// Tests queries when cache key/val types are generic.
+        /// </summary>
+        [Test]
+        public void TestNestedGenericCacheTypes()
+        {
+            var cfg = new CacheConfiguration(TestUtils.TestName)
+            {
+                QueryEntities = new[] {new QueryEntity(typeof(int), typeof(GenericTest<GenericTest2<string>>))}
+            };
+
+            var cache = Ignition.GetIgnite().GetOrCreateCache<int, GenericTest<GenericTest2<string>>>(cfg);
+            var key = 1;
+            var value = new GenericTest<GenericTest2<string>>(new GenericTest2<string>("foo"));
+            cache[key] = value;
+
+            var query = cache.AsCacheQueryable()
+                .Where(x => x.Value.Foo.Bar == value.Foo.Bar)
+                .Select(x => x.Value.Foo);
+
+            var sql = query.ToCacheQueryable().GetFieldsQuery().Sql;
+            var res = query.ToList();
+
+            Assert.AreEqual(1, res.Count);
+            Assert.AreEqual(value.Foo.Bar, res[0].Bar);
+
+            var expectedSql = string.Format("select _T0.FOO from \"{0}\".GENERICTEST as", cache.Name);
+            StringAssert.StartsWith(expectedSql, sql);
+        }
+
+        /// <summary>
+        /// Tests queries when cache val type has two generic type arguments.
+        /// </summary>
+        [Test]
+        public void TestTwoGenericArgumentsCacheType()
+        {
+            var cfg = new CacheConfiguration(TestUtils.TestName)
+            {
+                QueryEntities = new[] {new QueryEntity(typeof(int), typeof(GenericTest3<int, string, bool>))}
+            };
+
+            var cache = Ignition.GetIgnite().GetOrCreateCache<int, GenericTest3<int, string, bool>>(cfg);
+            var key = 1;
+            var value = new GenericTest3<int, string, bool>(2, "3", true);
+            cache[key] = value;
+
+            var query = cache.AsCacheQueryable()
+                .Where(x => x.Value.Baz == value.Baz && x.Value.Qux == value.Qux && x.Value.Quz)
+                .Select(x => x.Value.Baz);
+
+            var sql = query.ToCacheQueryable().GetFieldsQuery().Sql;
+            var res = query.ToList();
+
+            Assert.AreEqual(1, res.Count);
+            Assert.AreEqual(value.Baz, res[0]);
+
+            var expectedSql = string.Format("select _T0.BAZ from \"{0}\".GENERICTEST3 as", cache.Name);
+            StringAssert.StartsWith(expectedSql, sql);
+        }
+
+        /// <summary>
+        /// Generic query type.
+        /// </summary>
+        private class GenericTest<T>
+        {
+            /** */
+            public GenericTest(T foo)
+            {
+                Foo = foo;
+            }
+
+            /** */
+            [QuerySqlField]
+            public T Foo { get; set; }
+        }
+
+        /// <summary>
+        /// Generic query type.
+        /// </summary>
+        private class GenericTest2<T>
+        {
+            /** */
+            public GenericTest2(T bar)
+            {
+                Bar = bar;
+            }
+
+            /** */
+            [QuerySqlField]
+            public T Bar { get; set; }
+        }
+
+        /// <summary>
+        /// Generic query type with two generic arguments.
+        /// </summary>
+        private class GenericTest3<T, T2, T3>
+        {
+            /** */
+            public GenericTest3(T baz, T2 qux, T3 quz)
+            {
+                Baz = baz;
+                Qux = qux;
+                Quz = quz;
+            }
+
+            /** */
+            [QuerySqlField]
+            public T Baz { get; set; }
+            
+            /** */
+            [QuerySqlField]
+            public T2 Qux { get; set; }
+            
+            /** */
+            [QuerySqlField]
+            public T3 Quz { get; set; }
+        }
     }
 }