IGNITE-5315 .NET: Fix LINQ, examples, and cache configuration to account for IGNITE-5287 (SqlEscapeAll)
This closes #2023 Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/f5bbc71d Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/f5bbc71d Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/f5bbc71d Branch: refs/heads/ignite-5075 Commit: f5bbc71d4c1cd1a01b08dbc5c5df892bd2f55316 Parents: e0b2053 Author: Pavel Tupitsyn <[email protected]> Authored: Mon May 29 15:53:37 2017 +0300 Committer: Pavel Tupitsyn <[email protected]> Committed: Mon May 29 15:53:37 2017 +0300 ---------------------------------------------------------------------- .../Apache.Ignite.Core.Tests.csproj | 1 + .../Cache/CacheConfigurationTest.cs | 6 +- .../Cache/Query/CacheLinqTest.cs | 131 ++++++++++++++----- .../Cache/Query/CacheLinqTestSqlEscapeAll.cs | 34 +++++ .../Apache.Ignite.Core.csproj | 1 + .../Cache/Configuration/QueryEntity.cs | 64 +++++++-- .../Impl/Cache/IQueryEntityInternal.cs | 31 +++++ .../Impl/CacheFieldsQueryProvider.cs | 9 ++ .../Impl/CacheQueryExpressionVisitor.cs | 76 ++++++++--- .../Apache.Ignite.Linq/Impl/ExpressionWalker.cs | 7 +- 10 files changed, 295 insertions(+), 65 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/f5bbc71d/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj index f27e774..974f858 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Apache.Ignite.Core.Tests.csproj @@ -102,6 +102,7 @@ <Compile Include="Cache\Query\CacheDmlQueriesTest.cs" /> <Compile Include="Cache\CacheAbstractTransactionalTest.cs" /> <Compile Include="Cache\Query\CacheDmlQueriesTestSimpleName.cs" /> + <Compile Include="Cache\Query\CacheLinqTestSqlEscapeAll.cs" /> <Compile Include="Cache\Query\CacheLinqTestSimpleName.cs" /> <Compile Include="Cache\Query\CacheQueriesTestSimpleName.cs" /> <Compile Include="Cache\Query\Continuous\ContinuousQueryTest.cs" /> http://git-wip-us.apache.org/repos/asf/ignite/blob/f5bbc71d/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/CacheConfigurationTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/CacheConfigurationTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/CacheConfigurationTest.cs index cf70970..7935cc3 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/CacheConfigurationTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/CacheConfigurationTest.cs @@ -401,10 +401,11 @@ namespace Apache.Ignite.Core.Tests.Cache return; } - Assert.AreEqual(x.Count, y.Count); + // Resulting configuration may include additional aliases. + Assert.LessOrEqual(x.Count, y.Count); for (var i = 0; i < x.Count; i++) - AssertConfigsAreEqual(x.ElementAt(i), y.ElementAt(i)); + AssertConfigsAreEqual(x.ElementAt(i), y.FirstOrDefault(a => a.FullName == x.ElementAt(i).FullName)); } /// <summary> @@ -517,6 +518,7 @@ namespace Apache.Ignite.Core.Tests.Cache { KeyTypeName = "Integer", ValueTypeName = "java.lang.String", + TableName = "Table1", Fields = new[] { new QueryField("length", typeof(int)), http://git-wip-us.apache.org/repos/asf/ignite/blob/f5bbc71d/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 b603d75..04ce965 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 @@ -142,6 +142,14 @@ namespace Apache.Ignite.Core.Tests.Cache.Query } /// <summary> + /// Gets the SqlEscapeAll setting. + /// </summary> + protected virtual bool GetSqlEscapeAll() + { + return false; + } + + /// <summary> /// Fixture tear down. /// </summary> [TestFixtureTearDown] @@ -941,7 +949,10 @@ namespace Apache.Ignite.Core.Tests.Cache.Query public void TestNumerics() { var cache = Ignition.GetIgnite() - .GetOrCreateCache<int, Numerics>(new CacheConfiguration("numerics", typeof (Numerics))); + .GetOrCreateCache<int, Numerics>(new CacheConfiguration("numerics", typeof (Numerics)) + { + SqlEscapeAll = GetSqlEscapeAll() + }); for (var i = 0; i < 100; i++) cache[i] = new Numerics(((double) i - 50)/3); @@ -1221,10 +1232,14 @@ namespace Apache.Ignite.Core.Tests.Cache.Query public void TestPrimitiveCache() { // Create partitioned cache - var cache = - Ignition.GetIgnite() - .GetOrCreateCache<int, string>(new CacheConfiguration("primitiveCache", - new QueryEntity(typeof (int), typeof (string))) {CacheMode = CacheMode.Replicated}); + var cache = Ignition.GetIgnite() + .GetOrCreateCache<int, string>( + new CacheConfiguration("primitiveCache", + new QueryEntity(typeof(int), typeof(string))) + { + CacheMode = CacheMode.Replicated, + SqlEscapeAll = GetSqlEscapeAll() + }); var qry = cache.AsCacheQueryable(); @@ -1246,7 +1261,10 @@ namespace Apache.Ignite.Core.Tests.Cache.Query { // Create partitioned cache var cache = - Ignition.GetIgnite().GetOrCreateCache<int, int>(new CacheConfiguration("partCache", typeof (int))); + Ignition.GetIgnite().GetOrCreateCache<int, int>(new CacheConfiguration("partCache", typeof (int)) + { + SqlEscapeAll = GetSqlEscapeAll() + }); // Populate const int count = 100; @@ -1284,8 +1302,13 @@ namespace Apache.Ignite.Core.Tests.Cache.Query Assert.AreEqual(cache.Ignite, query.Ignite); var fq = query.GetFieldsQuery(); - Assert.AreEqual("select _T0._key, _T0._val from \"person_org\".Person as _T0 where (_T0._key > ?)", + + Assert.AreEqual( + GetSqlEscapeAll() + ? "select _T0._KEY, _T0._VAL from \"person_org\".\"Person\" as _T0 where (_T0.\"_KEY\" > ?)" + : "select _T0._KEY, _T0._VAL from \"person_org\".Person as _T0 where (_T0._KEY > ?)", fq.Sql); + Assert.AreEqual(new[] {10}, fq.Arguments); Assert.IsTrue(fq.Local); Assert.AreEqual(PersonCount - 11, cache.QueryFields(fq).GetAll().Count); @@ -1297,11 +1320,17 @@ namespace Apache.Ignite.Core.Tests.Cache.Query Assert.AreEqual(TimeSpan.FromSeconds(2.5), fq.Timeout); var str = query.ToString(); - Assert.AreEqual("CacheQueryable [CacheName=person_org, TableName=Person, Query=SqlFieldsQuery " + - "[Sql=select _T0._key, _T0._val from \"person_org\".Person as _T0 where " + - "(_T0._key > ?), Arguments=[10], " + - "Local=True, PageSize=999, EnableDistributedJoins=False, EnforceJoinOrder=True, " + - "Timeout=00:00:02.5000000, ReplicatedOnly=True, Colocated=True]]", str); + Assert.AreEqual(GetSqlEscapeAll() + ? "CacheQueryable [CacheName=person_org, TableName=Person, Query=SqlFieldsQuery " + + "[Sql=select _T0._KEY, _T0._VAL from \"person_org\".\"Person\" as _T0 where " + + "(_T0.\"_KEY\" > ?), Arguments=[10], " + + "Local=True, PageSize=999, EnableDistributedJoins=False, EnforceJoinOrder=True, " + + "Timeout=00:00:02.5000000, ReplicatedOnly=True, Colocated=True]]" + : "CacheQueryable [CacheName=person_org, TableName=Person, Query=SqlFieldsQuery " + + "[Sql=select _T0._KEY, _T0._VAL from \"person_org\".Person as _T0 where " + + "(_T0._KEY > ?), Arguments=[10], " + + "Local=True, PageSize=999, EnableDistributedJoins=False, EnforceJoinOrder=True, " + + "Timeout=00:00:02.5000000, ReplicatedOnly=True, Colocated=True]]", str); // Check fields query var fieldsQuery = (ICacheQueryable) cache.AsCacheQueryable().Select(x => x.Value.Name); @@ -1310,17 +1339,26 @@ namespace Apache.Ignite.Core.Tests.Cache.Query Assert.AreEqual(cache.Ignite, fieldsQuery.Ignite); fq = fieldsQuery.GetFieldsQuery(); - Assert.AreEqual("select _T0.Name from \"person_org\".Person as _T0", fq.Sql); + Assert.AreEqual(GetSqlEscapeAll() + ? "select _T0.\"Name\" from \"person_org\".\"Person\" as _T0" + : "select _T0.NAME from \"person_org\".Person as _T0", + fq.Sql); + Assert.IsFalse(fq.Local); Assert.AreEqual(SqlFieldsQuery.DefaultPageSize, fq.PageSize); Assert.IsFalse(fq.EnableDistributedJoins); Assert.IsFalse(fq.EnforceJoinOrder); str = fieldsQuery.ToString(); - Assert.AreEqual("CacheQueryable [CacheName=person_org, TableName=Person, Query=SqlFieldsQuery " + - "[Sql=select _T0.Name from \"person_org\".Person as _T0, Arguments=[], Local=False, " + - "PageSize=1024, EnableDistributedJoins=False, EnforceJoinOrder=False, " + - "Timeout=00:00:00, ReplicatedOnly=False, Colocated=False]]", str); + Assert.AreEqual(GetSqlEscapeAll() + ? "CacheQueryable [CacheName=person_org, TableName=Person, Query=SqlFieldsQuery " + + "[Sql=select _T0.\"Name\" from \"person_org\".\"Person\" as _T0, Arguments=[], Local=False, " + + "PageSize=1024, EnableDistributedJoins=False, EnforceJoinOrder=False, " + + "Timeout=00:00:00, ReplicatedOnly=False, Colocated=False]]" + : "CacheQueryable [CacheName=person_org, TableName=Person, Query=SqlFieldsQuery " + + "[Sql=select _T0.NAME from \"person_org\".Person as _T0, Arguments=[], Local=False, " + + "PageSize=1024, EnableDistributedJoins=False, EnforceJoinOrder=False, " + + "Timeout=00:00:00, ReplicatedOnly=False, Colocated=False]]", str); // Check distributed joins flag propagation var distrQuery = cache.AsCacheQueryable(new QueryOptions {EnableDistributedJoins = true}) @@ -1331,12 +1369,19 @@ namespace Apache.Ignite.Core.Tests.Cache.Query Assert.IsTrue(query.GetFieldsQuery().EnableDistributedJoins); str = distrQuery.ToString(); - Assert.AreEqual("CacheQueryable [CacheName=person_org, TableName=Person, Query=SqlFieldsQuery " + - "[Sql=select _T0._key, _T0._val from \"person_org\".Person as _T0 where " + - "(((_T0._key > ?) and (_T0.age1 > ?)) " + - "and (_T0.Name like \'%\' || ? || \'%\') ), Arguments=[10, 20, x], Local=False, " + - "PageSize=1024, EnableDistributedJoins=True, EnforceJoinOrder=False, " + - "Timeout=00:00:00, ReplicatedOnly=False, Colocated=False]]", str); + Assert.AreEqual(GetSqlEscapeAll() + ? "CacheQueryable [CacheName=person_org, TableName=Person, Query=SqlFieldsQuery " + + "[Sql=select _T0._KEY, _T0._VAL from \"person_org\".\"Person\" as _T0 where " + + "(((_T0.\"_KEY\" > ?) and (_T0.\"age1\" > ?)) " + + "and (_T0.\"Name\" like \'%\' || ? || \'%\') ), Arguments=[10, 20, x], Local=False, " + + "PageSize=1024, EnableDistributedJoins=True, EnforceJoinOrder=False, " + + "Timeout=00:00:00, ReplicatedOnly=False, Colocated=False]]" + : "CacheQueryable [CacheName=person_org, TableName=Person, Query=SqlFieldsQuery " + + "[Sql=select _T0._KEY, _T0._VAL from \"person_org\".Person as _T0 where " + + "(((_T0._KEY > ?) and (_T0.AGE1 > ?)) " + + "and (_T0.NAME like \'%\' || ? || \'%\') ), Arguments=[10, 20, x], Local=False, " + + "PageSize=1024, EnableDistributedJoins=True, EnforceJoinOrder=False, " + + "Timeout=00:00:00, ReplicatedOnly=False, Colocated=False]]", str); } /// <summary> @@ -1377,12 +1422,18 @@ namespace Apache.Ignite.Core.Tests.Cache.Query // Create and populate partitioned caches var personCache = ignite.CreateCache<int, Person>(new CacheConfiguration("partitioned_persons", - new QueryEntity(typeof(int), typeof(Person)))); + new QueryEntity(typeof(int), typeof(Person))) + { + SqlEscapeAll = GetSqlEscapeAll() + }); personCache.PutAll(GetSecondPersonCache().ToDictionary(x => x.Key, x => x.Value)); var roleCache = ignite.CreateCache<int, Role>(new CacheConfiguration("partitioned_roles", - new QueryEntity(typeof(int), typeof(Role)))); + new QueryEntity(typeof(int), typeof(Role))) + { + SqlEscapeAll = GetSqlEscapeAll() + }); roleCache.PutAll(GetRoleCache().ToDictionary(x => x.Key.Foo, x => x.Value)); @@ -1432,7 +1483,10 @@ namespace Apache.Ignite.Core.Tests.Cache.Query { // Use new cache to avoid touching static data. var cache = Ignition.GetIgnite().CreateCache<int, Person>(new CacheConfiguration("deleteAllTest", - new QueryEntity(typeof(int), typeof(Person)))); + new QueryEntity(typeof(int), typeof(Person))) + { + SqlEscapeAll = GetSqlEscapeAll() + }); Enumerable.Range(1, 10).ToList().ForEach(x => cache.Put(x, new Person(x, x.ToString()))); @@ -1491,7 +1545,7 @@ namespace Apache.Ignite.Core.Tests.Cache.Query /// Gets the person cache. /// </summary> /// <returns></returns> - private static ICache<int, Person> GetPersonCache() + private ICache<int, Person> GetPersonCache() { return GetCacheOf<Person>(); } @@ -1500,7 +1554,7 @@ namespace Apache.Ignite.Core.Tests.Cache.Query /// Gets the org cache. /// </summary> /// <returns></returns> - private static ICache<int, Organization> GetOrgCache() + private ICache<int, Organization> GetOrgCache() { return GetCacheOf<Organization>(); } @@ -1508,7 +1562,7 @@ namespace Apache.Ignite.Core.Tests.Cache.Query /// <summary> /// Gets the cache. /// </summary> - private static ICache<int, T> GetCacheOf<T>() + private ICache<int, T> GetCacheOf<T>() { return Ignition.GetIgnite() .GetOrCreateCache<int, T>(new CacheConfiguration(PersonOrgCacheName, @@ -1527,23 +1581,31 @@ namespace Apache.Ignite.Core.Tests.Cache.Query new QueryField("MyValue", typeof(T)), } }, - new QueryEntity(typeof (int), typeof (Organization))) {CacheMode = CacheMode.Replicated}); + new QueryEntity(typeof (int), typeof (Organization))) + { + CacheMode = CacheMode.Replicated, + SqlEscapeAll = GetSqlEscapeAll() + }); } /// <summary> /// Gets the role cache. /// </summary> - private static ICache<RoleKey, Role> GetRoleCache() + private ICache<RoleKey, Role> GetRoleCache() { return Ignition.GetIgnite() .GetOrCreateCache<RoleKey, Role>(new CacheConfiguration(RoleCacheName, - new QueryEntity(typeof (RoleKey), typeof (Role))) {CacheMode = CacheMode.Replicated}); + new QueryEntity(typeof (RoleKey), typeof (Role))) + { + CacheMode = CacheMode.Replicated, + SqlEscapeAll = GetSqlEscapeAll() + }); } /// <summary> /// Gets the second person cache. /// </summary> - private static ICache<int, Person> GetSecondPersonCache() + private ICache<int, Person> GetSecondPersonCache() { return Ignition.GetIgnite() .GetOrCreateCache<int, Person>( @@ -1553,7 +1615,8 @@ namespace Apache.Ignite.Core.Tests.Cache.Query TableName = "CustomPersons" }) { - CacheMode = CacheMode.Replicated + CacheMode = CacheMode.Replicated, + SqlEscapeAll = GetSqlEscapeAll() }); } http://git-wip-us.apache.org/repos/asf/ignite/blob/f5bbc71d/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheLinqTestSqlEscapeAll.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheLinqTestSqlEscapeAll.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheLinqTestSqlEscapeAll.cs new file mode 100644 index 0000000..7473be9 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheLinqTestSqlEscapeAll.cs @@ -0,0 +1,34 @@ +/* + * 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.Core.Tests.Cache.Query +{ + using NUnit.Framework; + + /// <summary> + /// LINQ test with simple name mapper. + /// </summary> + [TestFixture] + public class CacheLinqTestSqlEscapeAll : CacheLinqTest + { + /** <inheritdoc /> */ + protected override bool GetSqlEscapeAll() + { + return true; + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/f5bbc71d/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj index 25b9603..9ce9dd2 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj @@ -198,6 +198,7 @@ <Compile Include="Impl\Binary\ReflectionUtils.cs" /> <Compile Include="Cache\Affinity\AffinityFunctionBase.cs" /> <Compile Include="Impl\Binary\TypeNameParser.cs" /> + <Compile Include="Impl\Cache\IQueryEntityInternal.cs" /> <Compile Include="Impl\Cache\MemoryMetrics.cs" /> <Compile Include="Impl\Cache\Store\CacheStore.cs" /> <Compile Include="Impl\Cache\Store\ICacheStoreInternal.cs" /> http://git-wip-us.apache.org/repos/asf/ignite/blob/f5bbc71d/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryEntity.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryEntity.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryEntity.cs index b6163ee..58e5af9 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryEntity.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryEntity.cs @@ -27,13 +27,14 @@ namespace Apache.Ignite.Core.Cache.Configuration using System.Reflection; using Apache.Ignite.Core.Binary; using Apache.Ignite.Core.Impl.Binary; + using Apache.Ignite.Core.Impl.Cache; using Apache.Ignite.Core.Log; /// <summary> /// Query entity is a description of cache entry (composed of key and value) /// in a way of how it must be indexed and can be queried. /// </summary> - public class QueryEntity + public class QueryEntity : IQueryEntityInternal { /** */ private Type _keyType; @@ -47,6 +48,11 @@ namespace Apache.Ignite.Core.Cache.Configuration /** */ private string _keyTypeName; + /** */ + private Dictionary<string, string> _aliasMap; + + private ICollection<QueryAlias> _aliases; + /// <summary> /// Initializes a new instance of the <see cref="QueryEntity"/> class. /// </summary> @@ -180,7 +186,15 @@ namespace Apache.Ignite.Core.Cache.Configuration /// Example: {"parent.name" -> "parentName"}. /// </summary> [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] - public ICollection<QueryAlias> Aliases { get; set; } + public ICollection<QueryAlias> Aliases + { + get { return _aliases; } + set + { + _aliases = value; + _aliasMap = null; + } + } /// <summary> /// Gets or sets the query indexes. @@ -189,6 +203,32 @@ namespace Apache.Ignite.Core.Cache.Configuration public ICollection<QueryIndex> Indexes { get; set; } /// <summary> + /// Gets the alias by field name, or null when no match found. + /// This method constructs a dictionary lazily to perform lookups. + /// </summary> + string IQueryEntityInternal.GetAlias(string fieldName) + { + if (Aliases == null || Aliases.Count == 0) + { + return null; + } + + // PERF: No ToDictionary. + if (_aliasMap == null) + { + _aliasMap = new Dictionary<string, string>(Aliases.Count, StringComparer.InvariantCulture); + + foreach (var alias in Aliases) + { + _aliasMap[alias.FullName] = alias.Alias; + } + } + + string res; + return _aliasMap.TryGetValue(fieldName, out res) ? res : null; + } + + /// <summary> /// Initializes a new instance of the <see cref="QueryEntity"/> class. /// </summary> /// <param name="reader">The reader.</param> @@ -372,14 +412,17 @@ namespace Apache.Ignite.Core.Cache.Configuration { var columnName = attr.Name ?? memberInfo.Key.Name; - // No dot notation for indexes + // Dot notation is required for nested SQL fields. + if (parentPropName != null) + { + columnName = parentPropName + "." + columnName; + } + if (attr.IsIndexed) + { indexes.Add(new QueryIndexEx(columnName, attr.IsDescending, QueryIndexType.Sorted, attr.IndexGroups)); - - // Dot notation is required for nested SQL fields - if (parentPropName != null) - columnName = parentPropName + "." + columnName; + } fields.Add(new QueryField(columnName, memberInfo.Value) {IsKeyField = isKey}); @@ -390,11 +433,12 @@ namespace Apache.Ignite.Core.Cache.Configuration { var columnName = attr.Name ?? memberInfo.Key.Name; - // No dot notation for FullText index names - indexes.Add(new QueryIndexEx(columnName, false, QueryIndexType.FullText, null)); - if (parentPropName != null) + { columnName = parentPropName + "." + columnName; + } + + indexes.Add(new QueryIndexEx(columnName, false, QueryIndexType.FullText, null)); fields.Add(new QueryField(columnName, memberInfo.Value) {IsKeyField = isKey}); http://git-wip-us.apache.org/repos/asf/ignite/blob/f5bbc71d/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/IQueryEntityInternal.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/IQueryEntityInternal.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/IQueryEntityInternal.cs new file mode 100644 index 0000000..c312a52 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/IQueryEntityInternal.cs @@ -0,0 +1,31 @@ +/* + * 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.Core.Impl.Cache +{ + /// <summary> + /// Extended QueryEntity interface for internal needs. + /// </summary> + public interface IQueryEntityInternal + { + /// <summary> + /// Gets the alias by field name, or null when no match found. + /// This method constructs a dictionary lazily to perform lookups. + /// </summary> + string GetAlias(string fieldName); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/f5bbc71d/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryProvider.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryProvider.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryProvider.cs index c665fe7..cce89fd 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryProvider.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryProvider.cs @@ -217,6 +217,7 @@ namespace Apache.Ignite.Linq.Impl return EscapeTableName(validTableNames[0]); } + // Try with full type name (this works when TableName is not set). var valueTypeName = cacheValueType.FullName; if (validTableNames.Contains(valueTypeName, StringComparer.OrdinalIgnoreCase)) @@ -224,6 +225,14 @@ namespace Apache.Ignite.Linq.Impl return EscapeTableName(valueTypeName); } + // Remove namespace and nested class qualification and try again. + valueTypeName = EscapeTableName(valueTypeName); + + if (validTableNames.Contains(valueTypeName, StringComparer.OrdinalIgnoreCase)) + { + return valueTypeName; + } + throw new CacheException(string.Format("Table name cannot be inferred for cache '{0}', " + "please use AsCacheQueryable overload with tableName parameter. " + "Valid table names: {1}", _cacheConfiguration.Name ?? "null", http://git-wip-us.apache.org/repos/asf/ignite/blob/f5bbc71d/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 8fa0b5d..1b42aad 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs @@ -29,6 +29,7 @@ namespace Apache.Ignite.Linq.Impl using System.Reflection; using Apache.Ignite.Core.Cache; using Apache.Ignite.Core.Cache.Configuration; + using Apache.Ignite.Core.Impl.Cache; using Apache.Ignite.Core.Impl.Common; using Remotion.Linq; using Remotion.Linq.Clauses; @@ -278,10 +279,10 @@ namespace Apache.Ignite.Linq.Impl // 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" + ? "{0}.*, {0}._KEY, {0}._VAL" : _useStar ? "{0}.*" - : "{0}._key, {0}._val"; + : "{0}._KEY, {0}._VAL"; var tableName = Aliases.GetTableAlias(expression); @@ -308,7 +309,7 @@ namespace Apache.Ignite.Linq.Impl if (queryable != null) { - var fieldName = GetFieldName(expression, queryable); + var fieldName = GetEscapedFieldName(expression, queryable); ResultBuilder.AppendFormat("{0}.{1}", Aliases.GetTableAlias(expression), fieldName); } @@ -319,9 +320,21 @@ namespace Apache.Ignite.Linq.Impl } /// <summary> - /// Gets the name of the field from a member expression. + /// Gets the name of the field from a member expression, with quotes when necessary. /// </summary> - private static string GetFieldName(MemberExpression expression, ICacheQueryableInternal queryable) + private static string GetEscapedFieldName(MemberExpression expression, ICacheQueryableInternal queryable) + { + var sqlEscapeAll = queryable.CacheConfiguration.SqlEscapeAll; + var fieldName = GetFieldName(expression, queryable); + + return sqlEscapeAll ? string.Format("\"{0}\"", fieldName) : fieldName; + } + + /// <summary> + /// Gets the name of the field from a member expression, with quotes when necessary. + /// </summary> + private static string GetFieldName(MemberExpression expression, ICacheQueryableInternal queryable, + bool ignoreAlias = false) { var fieldName = GetMemberFieldName(expression.Member); @@ -329,20 +342,18 @@ namespace Apache.Ignite.Linq.Impl var cacheCfg = queryable.CacheConfiguration; if (cacheCfg.QueryEntities == null || cacheCfg.QueryEntities.All(x => x.Aliases == null)) - return fieldName; // There are no aliases defined - early exit + { + // There are no aliases defined - early exit. + return fieldName; + } // Find query entity by key-val types - var keyValTypes = queryable.ElementType.GetGenericArguments(); - - Debug.Assert(keyValTypes.Length == 2); - - var entity = cacheCfg.QueryEntities.FirstOrDefault(e => - e.Aliases != null && - (e.KeyType == keyValTypes[0] || e.KeyTypeName == keyValTypes[0].FullName) && - (e.ValueType == keyValTypes[1] || e.ValueTypeName == keyValTypes[1].FullName)); + var entity = GetQueryEntity(queryable, cacheCfg); if (entity == null) + { return fieldName; + } // There are some aliases for the current query type // Calculate full field name and look for alias @@ -351,15 +362,44 @@ namespace Apache.Ignite.Linq.Impl while ((member = member.Expression as MemberExpression) != null && member.Member.DeclaringType != queryable.ElementType) - fullFieldName = GetFieldName(member, queryable) + "." + fullFieldName; + { + fullFieldName = GetFieldName(member, queryable, true) + "." + fullFieldName; + } - var alias = entity.Aliases.Where(x => x.FullName == fullFieldName) - .Select(x => x.Alias).FirstOrDefault(); + var alias = ignoreAlias ? null : ((IQueryEntityInternal)entity).GetAlias(fullFieldName); return alias ?? fieldName; } /// <summary> + /// Finds matching query entity in the cache configuration. + /// </summary> + private static QueryEntity GetQueryEntity(ICacheQueryableInternal queryable, CacheConfiguration cacheCfg) + { + if (cacheCfg.QueryEntities.Count == 1) + { + return cacheCfg.QueryEntities.Single(); + } + + var keyValTypes = queryable.ElementType.GetGenericArguments(); + + Debug.Assert(keyValTypes.Length == 2); + + // PERF: No LINQ. + foreach (var e in cacheCfg.QueryEntities) + { + if (e.Aliases != null + && (e.KeyType == keyValTypes[0] || e.KeyTypeName == keyValTypes[0].FullName) + && (e.ValueType == keyValTypes[1] || e.ValueTypeName == keyValTypes[1].FullName)) + { + return e; + } + } + + return null; + } + + /// <summary> /// Gets the name of the member field. /// </summary> [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", @@ -377,7 +417,7 @@ namespace Apache.Ignite.Linq.Impl if (m.DeclaringType != null && m.DeclaringType.IsGenericType && m.DeclaringType.GetGenericTypeDefinition() == typeof (ICacheEntry<,>)) - return "_" + m.Name.ToLowerInvariant().Substring(0, 3); + return "_" + m.Name.ToUpperInvariant().Substring(0, 3); var qryFieldAttr = m.GetCustomAttributes(true) .OfType<QuerySqlFieldAttribute>().FirstOrDefault(); http://git-wip-us.apache.org/repos/asf/ignite/blob/f5bbc71d/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 4407f96..f00a13b 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/ExpressionWalker.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/ExpressionWalker.cs @@ -176,7 +176,12 @@ namespace Apache.Ignite.Linq.Impl { Debug.Assert(queryable != null); - return string.Format("\"{0}\".{1}", queryable.CacheConfiguration.Name, queryable.TableName); + var cacheCfg = queryable.CacheConfiguration; + + return string.Format(cacheCfg.SqlEscapeAll + ? "\"{0}\".\"{1}\"" + : "\"{0}\".{1}", + cacheCfg.Name, queryable.TableName); } } }
