IGNITE-1630: .NET: Added LINQ support. This closes #482.
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/ef642e91 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/ef642e91 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/ef642e91 Branch: refs/heads/ignite-2004 Commit: ef642e9180e4792bc7b22d57f7f88ec7d5805a84 Parents: 75f720f Author: Pavel Tupitsyn <[email protected]> Authored: Thu Mar 31 13:35:24 2016 +0300 Committer: vozerov-gridgain <[email protected]> Committed: Thu Mar 31 13:35:24 2016 +0300 ---------------------------------------------------------------------- .../Apache.Ignite.Core.Tests.NuGet.csproj | 25 +- .../Apache.Ignite.Core.Tests.NuGet/CacheTest.cs | 37 +- .../Apache.Ignite.Core.Tests.NuGet/NuGet.config | 5 +- .../install-package.cmd | 10 - .../install-package.ps1 | 25 + .../packages.config | 24 + .../Apache.Ignite.Core.Tests.csproj | 5 + .../Cache/Query/CacheLinqTest.cs | 1278 ++++++++++++++++++ .../Apache.Ignite.Core.Tests/TestRunner.cs | 2 +- .../Apache.Ignite.Core.csproj | 2 + .../Cache/Configuration/QueryEntity.cs | 4 +- .../Cache/Configuration/QueryField.cs | 2 +- .../dotnet/Apache.Ignite.Core/Ignition.cs | 2 +- .../Impl/Binary/BinaryReflectiveActions.cs | 37 + .../Apache.Ignite.Core/Impl/Binary/JavaTypes.cs | 29 +- .../Apache.Ignite.Core/Impl/Cache/CacheEntry.cs | 2 +- .../Apache.Ignite.Core/Impl/Cache/CacheImpl.cs | 37 +- .../Impl/Cache/ICacheInternal.cs | 40 + .../Impl/Cache/Query/FieldsQueryCursor.cs | 26 +- .../Impl/Common/DelegateConverter.cs | 127 +- .../Apache.Ignite.Core/Impl/Common/Logger.cs | 37 + .../Impl/Common/TypeCaster.cs | 12 + .../Apache.Ignite.Core/Impl/IgniteManager.cs | 2 +- .../Apache.Ignite.Linq.csproj | 93 ++ .../Apache.Ignite.Linq.nuspec | 63 + .../Apache.Ignite.Linq/Apache.Ignite.Linq.snk | Bin 0 -> 596 bytes .../Apache.Ignite.Linq/CacheExtensions.cs | 98 ++ .../dotnet/Apache.Ignite.Linq/CompiledQuery.cs | 206 +++ .../Apache.Ignite.Linq/ICacheQueryable.cs | 53 + .../Apache.Ignite.Linq/Impl/AliasDictionary.cs | 102 ++ .../Impl/CacheFieldsQueryExecutor.cs | 225 +++ .../Impl/CacheFieldsQueryProvider.cs | 239 ++++ .../Impl/CacheFieldsQueryable.cs | 40 + .../Impl/CacheQueryExpressionVisitor.cs | 506 +++++++ .../Impl/CacheQueryModelVisitor.cs | 508 +++++++ .../Apache.Ignite.Linq/Impl/CacheQueryParser.cs | 56 + .../Apache.Ignite.Linq/Impl/CacheQueryable.cs | 43 + .../Impl/CacheQueryableBase.cs | 122 ++ .../Apache.Ignite.Linq/Impl/ExpressionWalker.cs | 172 +++ .../Apache.Ignite.Linq/Impl/ICacheQueryProxy.cs | 40 + .../Impl/ICacheQueryableInternal.cs | 54 + .../Apache.Ignite.Linq/Impl/MethodVisitor.cs | 249 ++++ .../dotnet/Apache.Ignite.Linq/Impl/QueryData.cs | 92 ++ .../dotnet/Apache.Ignite.Linq/Impl/SqlTypes.cs | 63 + .../NuGet/LINQPad/QueryExample.linq | 111 ++ .../Properties/AssemblyInfo.cs | 40 + .../dotnet/Apache.Ignite.Linq/packages.config | 22 + modules/platforms/dotnet/Apache.Ignite.sln | 16 +- .../Apache.Ignite.Examples.csproj | 5 + .../Datagrid/LinqExample.cs | 253 ++++ 50 files changed, 5188 insertions(+), 53 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/Apache.Ignite.Core.Tests.NuGet.csproj ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/Apache.Ignite.Core.Tests.NuGet.csproj b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/Apache.Ignite.Core.Tests.NuGet.csproj index 4283de4..3ff1a37 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/Apache.Ignite.Core.Tests.NuGet.csproj +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/Apache.Ignite.Core.Tests.NuGet.csproj @@ -54,7 +54,13 @@ <ItemGroup> <Reference Include="Apache.Ignite.Core"> <SpecificVersion>False</SpecificVersion> - <HintPath>pkg\lib\net40\Apache.Ignite.Core.dll</HintPath> + <HintPath>packages\Apache.Ignite.1.6.0\lib\net40\Apache.Ignite.Core.dll</HintPath> + <Private>True</Private> + </Reference> + <Reference Include="Apache.Ignite.Linq"> + <SpecificVersion>False</SpecificVersion> + <HintPath>packages\Apache.Ignite.Linq.1.6.0\lib\net40\Apache.Ignite.Linq.dll</HintPath> + <Private>True</Private> </Reference> <Reference Include="nunit-console-runner"> <HintPath>..\libs\nunit-console-runner.dll</HintPath> @@ -63,6 +69,10 @@ <SpecificVersion>False</SpecificVersion> <HintPath>..\libs\nunit.framework.dll</HintPath> </Reference> + <Reference Include="Remotion.Linq, Version=2.0.0.0, Culture=neutral, PublicKeyToken=fee00910d6e5f53b, processorArchitecture=MSIL"> + <HintPath>packages\Remotion.Linq.2.0.1\lib\net40\Remotion.Linq.dll</HintPath> + <Private>True</Private> + </Reference> <Reference Include="System" /> <Reference Include="System.Core" /> </ItemGroup> @@ -81,17 +91,20 @@ </ItemGroup> <ItemGroup> <None Include="Apache.Ignite.Core.Tests.NuGet.sln.DotSettings" /> - <None Include="install-package.cmd" /> + <None Include="install-package.ps1" /> <None Include="NuGet.config" /> + <None Include="packages.config"> + <SubType>Designer</SubType> + </None> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <PropertyGroup> - <PostBuildEvent>if not exist $(TargetDir)Libs md $(TargetDir)Libs -xcopy /s /y $(ProjectDir)pkg\Libs $(TargetDir)Libs</PostBuildEvent> + <PostBuildEvent>if not exist "$(TargetDir)Libs" md "$(TargetDir)Libs" +xcopy /s /y "$(SolutionDir)packages\Apache.Ignite.1.6.0\Libs\*.*" "$(TargetDir)Libs"</PostBuildEvent> </PropertyGroup> <PropertyGroup> - <PreBuildEvent>cd $(ProjectDir) -if not exist pkg install-package.cmd</PreBuildEvent> + <PreBuildEvent> + </PreBuildEvent> </PropertyGroup> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/CacheTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/CacheTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/CacheTest.cs index ba0487b..6606d1e 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/CacheTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/CacheTest.cs @@ -20,8 +20,10 @@ namespace Apache.Ignite.Core.Tests.NuGet { using System.Linq; using Apache.Ignite.Core.Binary; + using Apache.Ignite.Core.Cache; using Apache.Ignite.Core.Cache.Configuration; using Apache.Ignite.Core.Cache.Query; + using Apache.Ignite.Linq; using NUnit.Framework; /// <summary> @@ -74,11 +76,7 @@ namespace Apache.Ignite.Core.Tests.NuGet [Test] public void TestSql() { - var ignite = Ignition.GetIgnite(); - - var cache = ignite.GetOrCreateCache<int, Person>(new CacheConfiguration("sqlCache", typeof (Person))); - - cache.PutAll(Enumerable.Range(1, 100).ToDictionary(x => x, x => new Person {Name = "Name" + x, Age = x})); + var cache = GetPersonCache(); var sqlRes = cache.Query(new SqlQuery(typeof (Person), "age < ?", 30)).GetAll(); @@ -87,6 +85,35 @@ namespace Apache.Ignite.Core.Tests.NuGet } /// <summary> + /// Tests the LINQ. + /// </summary> + [Test] + public void TestLinq() + { + var cache = GetPersonCache().AsCacheQueryable(); + + var res = cache.Where(x => x.Value.Age < 30).ToList(); + + Assert.AreEqual(29, res.Count); + Assert.IsTrue(res.All(x => x.Value.Age < 30)); + } + + /// <summary> + /// Gets the person cache. + /// </summary> + /// <returns></returns> + private static ICache<int, Person> GetPersonCache() + { + var ignite = Ignition.GetIgnite(); + + var cache = ignite.GetOrCreateCache<int, Person>(new CacheConfiguration("sqlCache", typeof(Person))); + + cache.PutAll(Enumerable.Range(1, 100).ToDictionary(x => x, x => new Person { Name = "Name" + x, Age = x })); + + return cache; + } + + /// <summary> /// Query class. /// </summary> private class Person http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/NuGet.config ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/NuGet.config b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/NuGet.config index 5099ff0..fc0c0b5 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/NuGet.config +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/NuGet.config @@ -25,10 +25,11 @@ <add key="automatic" value="True" /> </packageRestore> <packageSources> - <add key="ParentFolder" value="..\" /> + <add key="nuget.org" value="https://www.nuget.org/api/v2/" /> + <add key="local" value="nupkg" /> </packageSources> <!-- Used to specify which one of the sources are active --> <activePackageSource> - <add key="ParentFolder" value="..\" /> + <add key="All" value="(Aggregate source)" /> </activePackageSource> </configuration> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/install-package.cmd ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/install-package.cmd b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/install-package.cmd deleted file mode 100644 index 5af7607..0000000 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/install-package.cmd +++ /dev/null @@ -1,10 +0,0 @@ -rem Install NuGet package to a 'pkg' folder no matter what version it is - -rmdir pkg /S /Q -rmdir tmp1 /S /Q -mkdir tmp1 -cd tmp1 -nuget install Apache.Ignite -cd .. -move tmp1\Apache.Ignite.* pkg -rmdir tmp1 /S /Q \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/install-package.ps1 ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/install-package.ps1 b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/install-package.ps1 new file mode 100644 index 0000000..01c0be4 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/install-package.ps1 @@ -0,0 +1,25 @@ +$ng = (Get-Item .).FullName + '\nuget.exe' + +if (!(Test-Path $ng)) { + $ng = 'nuget' +} + +rmdir nupkg -Force -Recurse +rmdir pkg -Force -Recurse + +mkdir nupkg +mkdir pkg + +& $ng pack ..\Apache.Ignite.Core\Apache.Ignite.Core.csproj -Prop Configuration=Release -OutputDirectory nupkg +& $ng pack ..\Apache.Ignite.Linq\Apache.Ignite.Linq.csproj -Prop Configuration=Release -OutputDirectory nupkg + +$ver = (Get-ChildItem nupkg\Apache.Ignite.Linq*)[0].Name -replace '\D+([\d.]+)\.\D+','$1' + +# Replace versions in project files +(Get-Content packages.config) ` + -replace 'id="Apache.Ignite(.*?)" version=".*?"', ('id="Apache.Ignite$1" version="' + $ver + '"') ` + | Out-File packages.config -Encoding utf8 + +(Get-Content Apache.Ignite.Core.Tests.NuGet.csproj) ` + -replace '<HintPath>packages\\Apache.Ignite(.*?)\.[\d.]+\\', ('<HintPath>packages\Apache.Ignite$1.' + "$ver\") ` + | Out-File Apache.Ignite.Core.Tests.NuGet.csproj -Encoding utf8 \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/packages.config ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/packages.config b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/packages.config new file mode 100644 index 0000000..de80824 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests.NuGet/packages.config @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + 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. +--> + +<packages> + <package id="Apache.Ignite" version="1.6.0" targetFramework="net40" /> + <package id="Apache.Ignite.Linq" version="1.6.0" targetFramework="net40" /> + <package id="Remotion.Linq" version="2.0.1" targetFramework="net40" /> +</packages> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/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 6df25d4..9b946d6 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 @@ -71,6 +71,7 @@ <Compile Include="Cache\CachePartitionedTest.cs" /> <Compile Include="Cache\CacheReplicatedAtomicTest.cs" /> <Compile Include="Cache\CacheReplicatedTest.cs" /> + <Compile Include="Cache\Query\CacheLinqTest.cs" /> <Compile Include="Cache\Query\CacheQueriesCodeConfigurationTest.cs" /> <Compile Include="Cache\Query\Continuous\ContinuousQueryAbstractTest.cs" /> <Compile Include="Cache\Query\Continuous\ContinuousQueryAtomicBackupTest.cs" /> @@ -159,6 +160,10 @@ <Project>{4CD2F726-7E2B-46C4-A5BA-057BB82EECB6}</Project> <Name>Apache.Ignite.Core</Name> </ProjectReference> + <ProjectReference Include="..\Apache.Ignite.Linq\Apache.Ignite.Linq.csproj"> + <Project>{5b571661-17f4-4f29-8c7d-0edb38ca9b55}</Project> + <Name>Apache.Ignite.Linq</Name> + </ProjectReference> <ProjectReference Include="..\Apache.Ignite\Apache.Ignite.csproj"> <Project>{27F7F3C6-BDDE-43A9-B565-856F8395A04B}</Project> <Name>Apache.Ignite</Name> http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/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 new file mode 100644 index 0000000..1342256 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheLinqTest.cs @@ -0,0 +1,1278 @@ +/* + * 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. + */ + +// ReSharper disable SuspiciousTypeConversion.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable StringIndexOfIsCultureSpecific.1 +// ReSharper disable StringIndexOfIsCultureSpecific.2 +// ReSharper disable StringCompareToIsCultureSpecific +// ReSharper disable StringCompareIsCultureSpecific.1 +// ReSharper disable UnusedMemberInSuper.Global +namespace Apache.Ignite.Core.Tests.Cache.Query +{ + using System; + using System.Collections; + using System.Linq; + using System.Linq.Expressions; + using System.Text.RegularExpressions; + using System.Threading; + using Apache.Ignite.Core.Binary; + using Apache.Ignite.Core.Cache; + using Apache.Ignite.Core.Cache.Configuration; + using Apache.Ignite.Core.Discovery.Tcp; + using Apache.Ignite.Core.Discovery.Tcp.Static; + using Apache.Ignite.Linq; + using NUnit.Framework; + + /// <summary> + /// Tests LINQ. + /// </summary> + public class CacheLinqTest + { + /** Cache name. */ + private const string PersonOrgCacheName = null; + + /** Cache name. */ + private const string PersonSecondCacheName = "person_cache"; + + /** Role cache name. */ + private const string RoleCacheName = "role_cache"; + + /** */ + private const int RoleCount = 3; + + /** */ + private const int PersonCount = 900; + + /** */ + private bool _runDbConsole; + + /** */ + private static readonly DateTime StartDateTime = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + /// <summary> + /// Fixture set up. + /// </summary> + [TestFixtureSetUp] + public void FixtureSetUp() + { + _runDbConsole = false; // set to true to open H2 console + + if (_runDbConsole) + Environment.SetEnvironmentVariable("IGNITE_H2_DEBUG_CONSOLE", "true"); + + Ignition.Start(GetConfig()); + Ignition.Start(GetConfig("grid2")); + + // Populate caches + var cache = GetPersonCache(); + var personCache = GetSecondPersonCache(); + + for (var i = 0; i < PersonCount; i++) + { + cache.Put(i, new Person(i, string.Format(" Person_{0} ", i)) + { + Address = new Address {Zip = i, Street = "Street " + i, AliasTest = i}, + OrganizationId = i%2 + 1000, + Birthday = StartDateTime.AddYears(i), + AliasTest = -i + }); + + var i2 = i + PersonCount; + personCache.Put(i2, new Person(i2, "Person_" + i2) + { + Address = new Address {Zip = i2, Street = "Street " + i2}, + OrganizationId = i%2 + 1000, + Birthday = StartDateTime.AddYears(i) + }); + } + + var orgCache = GetOrgCache(); + + orgCache[1000] = new Organization {Id = 1000, Name = "Org_0"}; + orgCache[1001] = new Organization {Id = 1001, Name = "Org_1"}; + + var roleCache = GetRoleCache(); + + roleCache[new RoleKey(1, 101)] = new Role {Name = "Role_1", Date = StartDateTime}; + roleCache[new RoleKey(2, 102)] = new Role {Name = "Role_2", Date = StartDateTime.AddYears(1)}; + roleCache[new RoleKey(3, 103)] = new Role {Name = null, Date = StartDateTime.AddYears(2)}; + } + + /// <summary> + /// Gets the configuration. + /// </summary> + private static IgniteConfiguration GetConfig(string gridName = null) + { + return new IgniteConfiguration + { + JvmClasspath = TestUtils.CreateTestClasspath(), + JvmOptions = TestUtils.TestJavaOptions(), + BinaryConfiguration = new BinaryConfiguration(typeof(Person), + typeof(Organization), typeof(Address), typeof(Role), typeof(RoleKey), typeof(Numerics)), + DiscoverySpi = new TcpDiscoverySpi + { + IpFinder = new TcpDiscoveryStaticIpFinder + { + Endpoints = new[] { "127.0.0.1:47500", "127.0.0.1:47501" } + } + }, + GridName = gridName + }; + } + + /// <summary> + /// Fixture tear down. + /// </summary> + [TestFixtureTearDown] + public void FixtureTearDown() + { + if (_runDbConsole) + Thread.Sleep(Timeout.Infinite); + Ignition.StopAll(true); + } + + /// <summary> + /// Tests the empty query. + /// </summary> + [Test] + public void TestEmptyQuery() + { + // There are both persons and organizations in the same cache, but query should only return specific type + Assert.AreEqual(PersonCount, GetPersonCache().AsCacheQueryable().ToArray().Length); + Assert.AreEqual(RoleCount, GetRoleCache().AsCacheQueryable().ToArray().Length); + } + + /// <summary> + /// Tests where clause. + /// </summary> + [Test] + public void TestWhere() + { + var cache = GetPersonCache().AsCacheQueryable(); + + // Test const and var parameters + const int age = 10; + var key = 15; + + Assert.AreEqual(age, cache.Where(x => x.Value.Age < age).ToArray().Length); + Assert.AreEqual(age, cache.Where(x => x.Value.Address.Zip < age).ToArray().Length); + Assert.AreEqual(19, cache.Where(x => x.Value.Age > age && x.Value.Age < 30).ToArray().Length); + Assert.AreEqual(20, cache.Where(x => x.Value.Age > age).Count(x => x.Value.Age < 30 || x.Value.Age == 50)); + Assert.AreEqual(key, cache.Where(x => x.Key < key).ToArray().Length); + 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); + } + + /// <summary> + /// Tests the single field query. + /// </summary> + [Test] + public void TestSingleFieldQuery() + { + var cache = GetPersonCache().AsCacheQueryable(); + + // Multiple values + Assert.AreEqual(new[] {0, 1, 2}, + cache.Where(x => x.Key < 3).Select(x => x.Value.Address.Zip).ToArray()); + + // Single value + Assert.AreEqual(0, cache.Where(x => x.Key < 0).Select(x => x.Value.Age).FirstOrDefault()); + Assert.AreEqual(3, cache.Where(x => x.Key == 3).Select(x => x.Value.Age).FirstOrDefault()); + Assert.AreEqual(3, cache.Where(x => x.Key == 3).Select(x => x.Value).Single().Age); + Assert.AreEqual(3, cache.Select(x => x.Key).Single(x => x == 3)); + Assert.AreEqual(7, + cache.Select(x => x.Value) + .Where(x => x.Age == 7) + .Select(x => x.Address) + .Where(x => x.Zip > 0) + .Select(x => x.Zip) + .Single()); + } + + /// <summary> + /// Tests the field projection. + /// </summary> + [Test] + public void TestFieldProjection() + { + var cache = GetPersonCache().AsCacheQueryable(); + + // Project whole cache entry to anonymous class + Assert.AreEqual(5, cache.Where(x => x.Key == 5).Select(x => new { Foo = x }).Single().Foo.Key); + } + + /// <summary> + /// Tests the multi field query. + /// </summary> + [Test] + public void TestMultiFieldQuery() + { + var cache = GetPersonCache().AsCacheQueryable(); + + // Test anonymous type (ctor invoke) + var data = cache + .Select(x => new {Id = x.Key + 20, Age_ = x.Value.Age + 10, Addr = x.Value.Address}) + .Where(x => x.Id < 25) + .ToArray(); + + Assert.AreEqual(5, data.Length); + + foreach (var t in data) + { + Assert.AreEqual(t.Age_ - 10, t.Id - 20); + Assert.AreEqual(t.Age_ - 10, t.Addr.Zip); + } + } + + /// <summary> + /// Tests the scalar query. + /// </summary> + [Test] + public void TestScalarQuery() + { + var cache = GetPersonCache().AsCacheQueryable(); + + Assert.AreEqual(PersonCount - 1, cache.Max(x => x.Value.Age)); + Assert.AreEqual(0, cache.Min(x => x.Value.Age)); + + Assert.AreEqual(21, cache.Where(x => x.Key > 5 && x.Value.Age < 9).Select(x => x.Value.Age).Sum()); + + Assert.AreEqual(PersonCount, cache.Count()); + Assert.AreEqual(PersonCount, cache.Count(x => x.Key < PersonCount)); + } + + /// <summary> + /// Tests strings. + /// </summary> + [Test] + public void TestStrings() + { + var strings = GetSecondPersonCache().AsCacheQueryable().Select(x => x.Value.Name); + + CheckFunc(x => x.ToLower(), strings); + CheckFunc(x => x.ToUpper(), strings); + CheckFunc(x => x.StartsWith("Person_9"), strings); + CheckFunc(x => x.EndsWith("7"), strings); + CheckFunc(x => x.Contains("son_3"), strings); + CheckFunc(x => x.Length, strings); + + CheckFunc(x => x.IndexOf("9"), strings); + CheckFunc(x => x.IndexOf("7", 4), strings); + + CheckFunc(x => x.Substring(4), strings); + CheckFunc(x => x.Substring(4, 5), strings); + + CheckFunc(x => x.Trim(), strings); + CheckFunc(x => x.Trim('P'), strings); + CheckFunc(x => x.Trim('3'), strings); + CheckFunc(x => x.TrimStart('P'), strings); + CheckFunc(x => x.TrimStart('3'), strings); + Assert.Throws<NotSupportedException>(() => CheckFunc(x => x.TrimStart('P', 'e'), strings)); + CheckFunc(x => x.TrimEnd('P'), strings); + CheckFunc(x => x.TrimEnd('3'), strings); + + CheckFunc(x => Regex.Replace(x, @"son.\d", "kele!"), strings); + CheckFunc(x => x.Replace("son", ""), strings); + CheckFunc(x => x.Replace("son", "kele"), strings); + + // Concat + CheckFunc(x => x + x, strings); + + // String + int + CheckFunc(x => x + 10, strings); + } + + /// <summary> + /// Tests aggregates. + /// </summary> + [Test] + public void TestAggregates() + { + var cache = GetPersonCache().AsCacheQueryable(); + + Assert.AreEqual(PersonCount, cache.Count()); + Assert.AreEqual(PersonCount, cache.Select(x => x.Key).Count()); + Assert.AreEqual(2, cache.Select(x => x.Value.OrganizationId).Distinct().Count()); + + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + Assert.Throws<NotSupportedException>(() => cache.Select(x => new {x.Key, x.Value}).Count()); + + // Min/max/sum/avg + var ints = cache.Select(x => x.Key); + Assert.AreEqual(0, ints.Min()); + Assert.AreEqual(PersonCount - 1, ints.Max()); + Assert.AreEqual(ints.ToArray().Sum(), ints.Sum()); + Assert.AreEqual((int)ints.ToArray().Average(), (int)ints.Average()); + + var dupInts = ints.Select(x => x/10); // duplicate values + CollectionAssert.AreEquivalent(dupInts.ToArray().Distinct().ToArray(), dupInts.Distinct().ToArray()); + Assert.AreEqual(dupInts.ToArray().Distinct().Sum(), dupInts.Distinct().Sum()); + + // All/any + Assert.IsFalse(ints.Where(x => x > -5).Any(x => x > PersonCount && x > 0)); + Assert.IsTrue(ints.Any(x => x < PersonCount / 2)); + + // Skip/take + var keys = cache.Select(x => x.Key).OrderBy(x => x); + Assert.AreEqual(new[] {0, 1}, keys.Take(2).ToArray()); + Assert.AreEqual(new[] {1, 2}, keys.Skip(1).Take(2).ToArray()); + Assert.AreEqual(new[] {PersonCount - 2, PersonCount - 1}, keys.Skip(PersonCount - 2).ToArray()); + } + + /// <summary> + /// Tests aggregates with all clause. + /// </summary> + [Test] + [Ignore("IGNITE-2563")] + public void TestAggregatesAll() + { + var ints = GetPersonCache().AsCacheQueryable().Select(x => x.Key); + + Assert.IsTrue(ints.Where(x => x > -10).All(x => x < PersonCount && x >= 0)); + + Assert.IsFalse(ints.All(x => x < PersonCount / 2)); + } + + /// <summary> + /// Tests conditions. + /// </summary> + [Test] + public void TestConditions() + { + var persons = GetPersonCache().AsCacheQueryable(); + + var res = persons.Select(x => new {Foo = x.Key%2 == 0 ? "even" : "odd", x.Value}).ToArray(); + Assert.AreEqual("even", res[0].Foo); + Assert.AreEqual("odd", res[1].Foo); + + var roles = GetRoleCache().AsCacheQueryable(); + CheckFunc(x => x.Value.Name ?? "def_name", roles); + } + + /// <summary> + /// Tests the same cache join. + /// </summary> + [Test] + public void TestSameCacheJoin() + { + // Select persons in specific organization + var organizations = GetOrgCache().AsCacheQueryable(); + var persons = GetPersonCache().AsCacheQueryable(); + + var res = persons.Join(organizations, person => person.Value.OrganizationId + 3, org => org.Value.Id + 3, + (person, org) => new {Person = person.Value, Org = org.Value}) + .Where(x => x.Org.Name == "Org_1") + .ToList(); + + Assert.AreEqual(PersonCount / 2, res.Count); + + Assert.IsTrue(res.All(r => r.Person.OrganizationId == r.Org.Id)); + + // Test full projection (selects pair of ICacheEntry) + var res2 = persons.Join(organizations, person => person.Value.OrganizationId - 1, org => org.Value.Id - 1, + (person, org) => new {Person = person, Org = org}) + .Where(x => x.Org.Value.Name.ToLower() == "org_0") + .ToList(); + + Assert.AreEqual(PersonCount / 2, res2.Count); + } + + /// <summary> + /// Tests the multi key join. + /// </summary> + [Test] + public void TestMultiKeyJoin() + { + var organizations = GetOrgCache().AsCacheQueryable(); + var persons = GetPersonCache().AsCacheQueryable(); + + var multiKey = + from person in persons + join org in organizations on + new { OrgId = person.Value.OrganizationId, person.Key } equals + new { OrgId = org.Value.Id, Key = org.Key - 1000 } + where person.Key == 1 + select new { PersonName = person.Value.Name, OrgName = org.Value.Name }; + + Assert.AreEqual(" Person_1 ", multiKey.Single().PersonName); + } + + /// <summary> + /// Tests the cross cache join. + /// </summary> + [Test] + public void TestCrossCacheJoin() + { + var persons = GetPersonCache().AsCacheQueryable(); + var roles = GetRoleCache().AsCacheQueryable(); + + var res = persons.Join(roles, person => person.Key, role => role.Key.Foo, (person, role) => role) + .ToArray(); + + Assert.AreEqual(RoleCount, res.Length); + Assert.AreEqual(101, res[0].Key.Bar); + } + + /// <summary> + /// Tests the multi cache join. + /// </summary> + [Test] + public void TestMultiCacheJoin() + { + var organizations = GetOrgCache().AsCacheQueryable(); + var persons = GetPersonCache().AsCacheQueryable(); + var roles = GetRoleCache().AsCacheQueryable(); + + var res = roles.Join(persons, role => role.Key.Foo, person => person.Key, + (role, person) => new {person, role}) + .Join(organizations, pr => pr.person.Value.OrganizationId, org => org.Value.Id, + (pr, org) => new {org, pr.person, pr.role}).ToArray(); + + Assert.AreEqual(RoleCount, res.Length); + } + + /// <summary> + /// Tests the multi cache join subquery. + /// </summary> + [Test] + public void TestMultiCacheJoinSubquery() + { + var organizations = GetOrgCache().AsCacheQueryable().Where(x => x.Key == 1001); + var persons = GetPersonCache().AsCacheQueryable().Where(x => x.Key < 20); + var roles = GetRoleCache().AsCacheQueryable().Where(x => x.Key.Foo >= 0); + + var res = roles.Join(persons, role => role.Key.Foo, person => person.Key, + (role, person) => new {person, role}) + .Join(organizations, pr => pr.person.Value.OrganizationId, org => org.Value.Id, + (pr, org) => new {org, pr.person, pr.role}).ToArray(); + + Assert.AreEqual(2, res.Length); + } + + /// <summary> + /// Tests the outer join. + /// </summary> + [Test] + public void TestOuterJoin() + { + var persons = GetPersonCache().AsCacheQueryable(); + var roles = GetRoleCache().AsCacheQueryable(); + + var res = persons.Join(roles.Where(r => r.Key.Bar > 0).DefaultIfEmpty(), + person => person.Key, role => role.Key.Foo, + (person, role) => new + { + PersonName = person.Value.Name, + RoleName = role.Value.Name + }) + .Where(x => x.PersonName != " ") + .ToArray(); + + Assert.AreEqual(PersonCount, res.Length); + } + + /// <summary> + /// Tests the subquery join. + /// </summary> + [Test] + public void TestSubqueryJoin() + { + var persons = GetPersonCache().AsCacheQueryable().Where(x => x.Key >= 0); + + var orgs = GetOrgCache().AsCacheQueryable().Where(x => x.Key > 10); + + var res = persons.Join(orgs, + p => p.Value.OrganizationId, + o => o.Value.Id, (p, o) => p) + .Where(x => x.Key >= 0) + .ToList(); + + Assert.AreEqual(PersonCount, res.Count); + } + + /// <summary> + /// Tests the invalid join. + /// </summary> + [Test] + public void TestInvalidJoin() + { + // Join on non-IQueryable + Assert.Throws<NotSupportedException>(() => + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + GetPersonCache().AsCacheQueryable().Join(GetOrgCache(), p => p.Key, o => o.Key, (p, o) => p).ToList()); + } + + /// <summary> + /// Tests the multiple from. + /// </summary> + [Test] + public void TestMultipleFrom() + { + var persons = GetPersonCache().AsCacheQueryable().Where(x => x.Key < PersonCount); + var roles = GetRoleCache().AsCacheQueryable().Where(x => x.Value.Name != "1"); + + var all = persons.SelectMany(person => roles.Select(role => new { role, person })); + Assert.AreEqual(RoleCount * PersonCount, all.Count()); + + var filtered = + from person in persons + from role in roles + where person.Key == role.Key.Foo + select new {Person = person.Value.Name, Role = role.Value.Name}; + + var res = filtered.ToArray(); + + Assert.AreEqual(RoleCount, res.Length); + } + + /// <summary> + /// Tests the group by. + /// </summary> + [Test] + public void TestGroupBy() + { + var persons = GetPersonCache().AsCacheQueryable(); + var orgs = GetOrgCache().AsCacheQueryable(); + + // Simple, unordered + CollectionAssert.AreEquivalent(new[] {1000, 1001}, + persons.GroupBy(x => x.Value.OrganizationId).Select(x => x.Key).ToArray()); + + // Aggregate + Assert.AreEqual(1000, + persons.GroupBy(x => x.Value.OrganizationId).Select(x => x.Key).OrderBy(x => x).First()); + + // Ordering and count + var res1 = + from p in persons + orderby p.Value.Name + group p by p.Value.OrganizationId + into gs + orderby gs.Key + where gs.Count() > 10 + select new {Count = gs.Count(), OrgId = gs.Key, AvgAge = gs.Average(x => x.Value.Age)}; + + var resArr = res1.ToArray(); + + Assert.AreEqual(new[] + { + new {Count = PersonCount/2, OrgId = 1000, AvgAge = (double) PersonCount/2 - 1}, + new {Count = PersonCount/2, OrgId = 1001, AvgAge = (double) PersonCount/2} + }, resArr); + + // Join and sum + var res2 = persons.Join(orgs.Where(o => o.Key > 10), p => p.Value.OrganizationId, o => o.Key, + (p, o) => new {p, o}) + .GroupBy(x => x.o.Value.Name) + .Select(g => new {Org = g.Key, AgeSum = g.Select(x => x.p.Value.Age).Sum()}); + + var resArr2 = res2.ToArray(); + + Assert.AreEqual(new[] + { + new {Org = "Org_0", AgeSum = persons.Where(x => x.Value.OrganizationId == 1000).Sum(x => x.Value.Age)}, + new {Org = "Org_1", AgeSum = persons.Where(x => x.Value.OrganizationId == 1001).Sum(x => x.Value.Age)} + }, resArr2); + } + + /// <summary> + /// Tests the union. + /// </summary> + [Test] + public void TestUnion() + { + // Direct union + var persons = GetPersonCache().AsCacheQueryable(); + var persons2 = GetSecondPersonCache().AsCacheQueryable(); + + var res = persons.Union(persons2).ToArray(); + + Assert.AreEqual(PersonCount * 2, res.Length); + + // Subquery + var roles = GetRoleCache().AsCacheQueryable().Select(x => -x.Key.Foo); + var ids = GetPersonCache().AsCacheQueryable().Select(x => x.Key).Union(roles).ToArray(); + + Assert.AreEqual(RoleCount + PersonCount, ids.Length); + } + + /// <summary> + /// Tests intersect. + /// </summary> + [Test] + public void TestIntersect() + { + // Direct intersect + var persons = GetPersonCache().AsCacheQueryable(); + var persons2 = GetSecondPersonCache().AsCacheQueryable(); + + var res = persons.Intersect(persons2).ToArray(); + + Assert.AreEqual(0, res.Length); + + // Subquery + var roles = GetRoleCache().AsCacheQueryable().Select(x => x.Key.Foo); + var ids = GetPersonCache().AsCacheQueryable().Select(x => x.Key).Intersect(roles).ToArray(); + + Assert.AreEqual(RoleCount, ids.Length); + } + + /// <summary> + /// Tests except. + /// </summary> + [Test] + public void TestExcept() + { + // Direct except + var persons = GetPersonCache().AsCacheQueryable(); + var persons2 = GetSecondPersonCache().AsCacheQueryable(); + + var res = persons.Except(persons2).ToArray(); + + Assert.AreEqual(PersonCount, res.Length); + + // Subquery + var roles = GetRoleCache().AsCacheQueryable().Select(x => x.Key.Foo); + var ids = GetPersonCache().AsCacheQueryable().Select(x => x.Key).Except(roles).ToArray(); + + Assert.AreEqual(PersonCount - RoleCount, ids.Length); + } + + /// <summary> + /// Tests ordering. + /// </summary> + [Test] + public void TestOrdering() + { + var persons = GetPersonCache().AsCacheQueryable() + .OrderByDescending(x => x.Key) + .ThenBy(x => x.Value.Age) + .ToArray(); + + Assert.AreEqual(Enumerable.Range(0, PersonCount).Reverse().ToArray(), persons.Select(x => x.Key).ToArray()); + + var personsByOrg = GetPersonCache().AsCacheQueryable() + .Join(GetOrgCache().AsCacheQueryable(), p => p.Value.OrganizationId, o => o.Value.Id, + (p, o) => new + { + PersonId = p.Key, + PersonName = p.Value.Name.ToUpper(), + OrgName = o.Value.Name + }) + .OrderBy(x => x.OrgName.ToLower()) + .ThenBy(x => x.PersonName) + .ToArray(); + + var expectedIds = Enumerable.Range(0, PersonCount) + .OrderBy(x => (x%2).ToString()) + .ThenBy(x => x.ToString()) + .ToArray(); + + var actualIds = personsByOrg.Select(x => x.PersonId).ToArray(); + + Assert.AreEqual(expectedIds, actualIds); + } + + /// <summary> + /// Tests nulls. + /// </summary> + [Test] + public void TestNulls() + { + var roles = GetRoleCache().AsCacheQueryable(); + + var nullNameRole = roles.Single(x => x.Value.Name == null); + Assert.AreEqual(null, nullNameRole.Value.Name); + + var nonNullNameRoles = roles.Where(x => x.Value.Name != null); + Assert.AreEqual(RoleCount - 1, nonNullNameRoles.Count()); + } + + /// <summary> + /// Tests date time. + /// </summary> + [Test] + public void TestDateTime() + { + var roles = GetRoleCache().AsCacheQueryable(); + var persons = GetPersonCache().AsCacheQueryable(); + + // Invalid dateTime + var now = DateTime.Now; + // ReSharper disable once ReturnValueOfPureMethodIsNotUsed + Assert.Throws<InvalidOperationException>(() => roles.Where(x => x.Value.Date > now).ToArray()); + + // Test retrieval + var dates = roles.OrderBy(x => x.Value.Date).Select(x => x.Value.Date); + var expDates = new[] {StartDateTime, StartDateTime.AddYears(1), StartDateTime.AddYears(2)}; + Assert.AreEqual(expDates, dates.ToArray()); + + // Filtering + Assert.AreEqual(1, persons.Count(x => x.Value.Birthday == StartDateTime)); + Assert.AreEqual(PersonCount, persons.Count(x => x.Value.Birthday >= StartDateTime)); + + // Joins + var join = + from role in roles + join person in persons on role.Value.Date equals person.Value.Birthday + select person; + + Assert.AreEqual(RoleCount, join.Count()); + + // Functions + Assert.AreEqual("01 01 2000 03:00:00", dates.Select(x => x.ToString("DD MM YYYY HH:mm:ss")).First()); + } + + /// <summary> + /// Tests numerics. + /// </summary> + [Test] + public void TestNumerics() + { + var cache = Ignition.GetIgnite() + .GetOrCreateCache<int, Numerics>(new CacheConfiguration("numerics", typeof (Numerics))); + + for (var i = 0; i < 100; i++) + cache[i] = new Numerics(((double) i - 50)/3); + + var query = cache.AsCacheQueryable().Select(x => x.Value); + + var bytes = query.Select(x => x.Byte); + var sbytes = query.Select(x => x.Sbyte); + var shorts = query.Select(x => x.Short); + var ushorts = query.Select(x => x.Ushort); + var ints = query.Select(x => x.Int); + var uints = query.Select(x => x.Uint); + var longs = query.Select(x => x.Long); + var ulongs = query.Select(x => x.Ulong); + var doubles = query.Select(x => x.Double); + var decimals = query.Select(x => x.Decimal); + var floats = query.Select(x => x.Float); + + CheckFunc(x => Math.Abs(x), doubles); + CheckFunc(x => Math.Abs((sbyte) x), bytes); + CheckFunc(x => Math.Abs(x), sbytes); + CheckFunc(x => Math.Abs(x), shorts); + CheckFunc(x => Math.Abs((short) x), ushorts); + CheckFunc(x => Math.Abs(x), ints); + CheckFunc(x => Math.Abs((int) x), uints); + CheckFunc(x => Math.Abs(x), longs); + CheckFunc(x => Math.Abs((long) x), ulongs); + CheckFunc(x => Math.Abs(x), decimals); + CheckFunc(x => Math.Abs(x), floats); + + CheckFunc(x => Math.Acos(x), doubles); + CheckFunc(x => Math.Asin(x), doubles); + CheckFunc(x => Math.Atan(x), doubles); + CheckFunc(x => Math.Atan2(x, 0.5), doubles); + + CheckFunc(x => Math.Ceiling(x), doubles); + CheckFunc(x => Math.Ceiling(x), decimals); + + CheckFunc(x => Math.Cos(x), doubles); + CheckFunc(x => Math.Cosh(x), doubles); + CheckFunc(x => Math.Exp(x), doubles); + + CheckFunc(x => Math.Floor(x), doubles); + CheckFunc(x => Math.Floor(x), decimals); + + CheckFunc(x => Math.Log(x), doubles); + CheckFunc(x => Math.Log10(x), doubles); + + CheckFunc(x => Math.Pow(x, 3.7), doubles); + + CheckFunc(x => Math.Round(x), doubles); + CheckFunc(x => Math.Round(x, 3), doubles); + CheckFunc(x => Math.Round(x), decimals); + CheckFunc(x => Math.Round(x, 3), decimals); + + CheckFunc(x => Math.Sign(x), doubles); + CheckFunc(x => Math.Sign(x), decimals); + CheckFunc(x => Math.Sign(x), floats); + CheckFunc(x => Math.Sign(x), ints); + CheckFunc(x => Math.Sign(x), longs); + CheckFunc(x => Math.Sign(x), shorts); + CheckFunc(x => Math.Sign(x), sbytes); + + CheckFunc(x => Math.Sin(x), doubles); + CheckFunc(x => Math.Sinh(x), doubles); + CheckFunc(x => Math.Sqrt(x), doubles); + CheckFunc(x => Math.Tan(x), doubles); + CheckFunc(x => Math.Tanh(x), doubles); + + CheckFunc(x => Math.Truncate(x), doubles); + CheckFunc(x => Math.Truncate(x), decimals); + + // Operators + CheckFunc(x => x*7, doubles); + CheckFunc(x => x/7, doubles); + CheckFunc(x => x%7, doubles); + CheckFunc(x => x+7, doubles); + CheckFunc(x => x-7, doubles); + } + + /// <summary> + /// Tests aliases. + /// </summary> + [Test] + public void TestAliases() + { + var cache = GetPersonCache().AsCacheQueryable(); + + var res = cache.Where(x => x.Key == 1) + .Select(x => new {X = x.Value.AliasTest, Y = x.Value.Address.AliasTest}) + .Single(); + + Assert.AreEqual(new {X = -1, Y = 1}, res); + } + + /// <summary> + /// Tests the compiled query. + /// </summary> + [Test] + public void TestCompiledQuery() + { + var cache = GetPersonCache().AsCacheQueryable(); + + // 0 arg + var qry0 = CompiledQuery.Compile(() => cache.Select(x => x.Value.Name)); + Assert.AreEqual(PersonCount, qry0().ToArray().Length); + + // 1 arg + var qry1 = CompiledQuery.Compile((int k) => cache.Where(x => x.Key < k)); + Assert.AreEqual(3, qry1(3).ToArray().Length); + + // 2 arg + var qry2 = + CompiledQuery.Compile((int i, string s) => cache.Where(x => x.Key < i && x.Value.Name.StartsWith(s))); + Assert.AreEqual(5, qry2(5, " Pe").ToArray().Length); + + // Changed param order + var qry2R = + CompiledQuery.Compile((string s, int i) => cache.Where(x => x.Key < i && x.Value.Name.StartsWith(s))); + Assert.AreEqual(5, qry2R(" Pe", 5).ToArray().Length); + + // 3 arg + var qry3 = CompiledQuery.Compile((int i, string s, double d) => + cache.Where(x => x.Value.Address.Zip > d && x.Key < i && x.Value.Name.Contains(s))); + Assert.AreEqual(5, qry3(5, "son", -10).ToArray().Length); + + // 4 arg + var keys = cache.Select(x => x.Key); + var qry4 = CompiledQuery.Compile((int a, int b, int c, int d) => + keys.Where(k => k > a && k > b && k > c && k < d)); + Assert.AreEqual(new[] {3, 4}, qry4(0, 1, 2, 5).ToArray()); + + // 5 arg + var qry5 = CompiledQuery.Compile((int a, int b, int c, int d, int e) => + keys.Where(k => k > a && k > b && k > c && k < d && k < e)); + Assert.AreEqual(new[] {3, 4}, qry5(0, 1, 2, 5, 6).ToArray()); + + // 6 arg + var qry6 = CompiledQuery.Compile((int a, int b, int c, int d, int e, int f) => + keys.Where(k => k > a && k > b && k > c && k < d && k < e && k < f)); + Assert.AreEqual(new[] {3, 4}, qry6(0, 1, 2, 5, 6, 7).ToArray()); + + // 7 arg + var qry7 = CompiledQuery.Compile((int a, int b, int c, int d, int e, int f, int g) => + keys.Where(k => k > a && k > b && k > c && k < d && k < e && k < f && k < g)); + Assert.AreEqual(new[] {3, 4}, qry7(0, 1, 2, 5, 6, 7, 8).ToArray()); + + // 8 arg + var qry8 = CompiledQuery.Compile((int a, int b, int c, int d, int e, int f, int g, int h) => + keys.Where(k => k > a && k > b && k > c && k < d && k < e && k < f && k < g && k < h)); + Assert.AreEqual(new[] {3, 4}, qry8(0, 1, 2, 5, 6, 7, 8, 9).ToArray()); + } + + /// <summary> + /// Tests the cache of primitive types. + /// </summary> + [Test] + 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 qry = cache.AsCacheQueryable(); + + // Populate + const int count = 100; + cache.PutAll(Enumerable.Range(0, count).ToDictionary(x => x, x => x.ToString())); + + // Test + Assert.AreEqual(count, qry.ToArray().Length); + Assert.AreEqual(10, qry.Where(x => x.Key < 10).ToArray().Length); + Assert.AreEqual(1, qry.Count(x => x.Value.Contains("99"))); + } + + /// <summary> + /// Tests the local query. + /// </summary> + [Test] + public void TestLocalQuery() + { + // Create partitioned cache + var cache = + Ignition.GetIgnite().GetOrCreateCache<int, int>(new CacheConfiguration("partCache", typeof (int))); + + // Populate + const int count = 100; + cache.PutAll(Enumerable.Range(0, count).ToDictionary(x => x, x => x)); + + // Non-local query returns all records + Assert.AreEqual(count, cache.AsCacheQueryable(false).ToArray().Length); + + // Local query returns only some of the records + var localCount = cache.AsCacheQueryable(true).ToArray().Length; + Assert.Less(localCount, count); + Assert.Greater(localCount, 0); + } + + /// <summary> + /// Tests the introspection. + /// </summary> + [Test] + public void TestIntrospection() + { + var cache = GetPersonCache(); + + // Check regular query + var query = (ICacheQueryable) cache.AsCacheQueryable(true).Where(x => x.Key > 10); + + Assert.AreEqual(cache.Name, query.CacheName); + Assert.AreEqual(cache.Ignite, query.Ignite); + + var fq = query.GetFieldsQuery(); + Assert.AreEqual("select _T0._key, _T0._val from \"\".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); + + // Check fields query + var fieldsQuery = (ICacheQueryable) cache.AsCacheQueryable().Select(x => x.Value.Name); + + Assert.AreEqual(cache.Name, fieldsQuery.CacheName); + Assert.AreEqual(cache.Ignite, fieldsQuery.Ignite); + + fq = fieldsQuery.GetFieldsQuery(); + Assert.AreEqual("select _T0.Name from \"\".Person as _T0", fq.Sql); + Assert.IsFalse(fq.Local); + } + + /// <summary> + /// Tests the table name inference. + /// </summary> + [Test] + public void TestTableNameInference() + { + // Try with multi-type cache: explicit type is required + var cache = GetCacheOf<IPerson>(); + + Assert.Throws<CacheException>(() => cache.AsCacheQueryable()); + + var names = cache.AsCacheQueryable(false, "Person").Select(x => x.Value.Name).ToArray(); + + Assert.AreEqual(PersonCount, names.Length); + + // With single-type cache, interface inference works + var roleCache = Ignition.GetIgnite().GetCache<object, IRole>(RoleCacheName).AsCacheQueryable(); + + var roleNames = roleCache.Select(x => x.Value.Name).OrderBy(x => x).ToArray(); + + CollectionAssert.AreEquivalent(new[] {"Role_1", "Role_2", null}, roleNames); + + // Check non-queryable cache + var nonQueryableCache = Ignition.GetIgnite().GetOrCreateCache<Role, Person>("nonQueryable"); + + Assert.Throws<CacheException>(() => nonQueryableCache.AsCacheQueryable()); + } + + /// <summary> + /// Gets the person cache. + /// </summary> + /// <returns></returns> + private static ICache<int, Person> GetPersonCache() + { + return GetCacheOf<Person>(); + } + + /// <summary> + /// Gets the org cache. + /// </summary> + /// <returns></returns> + private static ICache<int, Organization> GetOrgCache() + { + return GetCacheOf<Organization>(); + } + + /// <summary> + /// Gets the cache. + /// </summary> + private static ICache<int, T> GetCacheOf<T>() + { + return Ignition.GetIgnite() + .GetOrCreateCache<int, T>(new CacheConfiguration(PersonOrgCacheName, + new QueryEntity(typeof (int), typeof (Person)) + { + Aliases = new[] + { + new QueryAlias("AliasTest", "Person_AliasTest"), + new QueryAlias("Address.AliasTest", "Addr_AliasTest") + } + }, + new QueryEntity(typeof (int), typeof (Organization))) {CacheMode = CacheMode.Replicated}); + } + + /// <summary> + /// Gets the role cache. + /// </summary> + private static ICache<RoleKey, Role> GetRoleCache() + { + return Ignition.GetIgnite() + .GetOrCreateCache<RoleKey, Role>(new CacheConfiguration(RoleCacheName, + new QueryEntity(typeof (RoleKey), typeof (Role))) {CacheMode = CacheMode.Replicated}); + } + + /// <summary> + /// Gets the second person cache. + /// </summary> + private static ICache<int, Person> GetSecondPersonCache() + { + return Ignition.GetIgnite() + .GetOrCreateCache<int, Person>(new CacheConfiguration(PersonSecondCacheName, + new QueryEntity(typeof (int), typeof (Person))) {CacheMode = CacheMode.Replicated}); + } + + /// <summary> + /// Checks that function maps to SQL function properly. + /// </summary> + private static void CheckFunc<T, TR>(Expression<Func<T, TR>> exp, IQueryable<T> query, + Func<TR, TR> localResultFunc = null) + { + localResultFunc = localResultFunc ?? (x => x); + + // Calculate result locally, using real method invocation + var expected = query.ToArray().AsQueryable().Select(exp).Select(localResultFunc).OrderBy(x => x).ToArray(); + + // Perform SQL query + var actual = query.Select(exp).ToArray().OrderBy(x => x).ToArray(); + + // Compare results + CollectionAssert.AreEqual(expected, actual, new NumericComparer()); + + // Perform intermediate anonymous type conversion to check type projection + actual = query.Select(exp).Select(x => new {Foo = x}).ToArray().Select(x => x.Foo) + .OrderBy(x => x).ToArray(); + + // Compare results + CollectionAssert.AreEqual(expected, actual, new NumericComparer()); + } + + public interface IPerson + { + int Age { get; set; } + string Name { get; set; } + } + + public class Person : IBinarizable, IPerson + { + public Person(int age, string name) + { + Age = age; + Name = name; + } + + [QuerySqlField(Name = "age1")] public int Age { get; set; } + + [QuerySqlField] public string Name { get; set; } + + [QuerySqlField] public Address Address { get; set; } + + [QuerySqlField] public int OrganizationId { get; set; } + + [QuerySqlField] public DateTime? Birthday { get; set; } + + [QuerySqlField] public int AliasTest { get; set; } + + public void WriteBinary(IBinaryWriter writer) + { + writer.WriteInt("age1", Age); + writer.WriteString("name", Name); + writer.WriteInt("OrganizationId", OrganizationId); + writer.WriteObject("Address", Address); + writer.WriteTimestamp("Birthday", Birthday); + writer.WriteInt("AliasTest", AliasTest); + } + + public void ReadBinary(IBinaryReader reader) + { + Age = reader.ReadInt("age1"); + Name = reader.ReadString("name"); + OrganizationId = reader.ReadInt("OrganizationId"); + Address = reader.ReadObject<Address>("Address"); + Birthday = reader.ReadTimestamp("Birthday"); + AliasTest = reader.ReadInt("AliasTest"); + } + } + + public class Address + { + [QuerySqlField] public int Zip { get; set; } + [QuerySqlField] public string Street { get; set; } + [QuerySqlField] public int AliasTest { get; set; } + } + + public class Organization + { + [QuerySqlField] public int Id { get; set; } + [QuerySqlField] public string Name { get; set; } + } + + public interface IRole + { + string Name { get; } + DateTime Date { get; } + } + + public class Role : IRole + { + [QuerySqlField] public string Name { get; set; } + [QuerySqlField] public DateTime Date { get; set; } + } + + public class Numerics + { + public Numerics(double val) + { + Double = val; + Float = (float) val; + Decimal = (decimal) val; + Int = (int) val; + Uint = (uint) val; + Long = (long) val; + Ulong = (ulong) val; + Short = (short) val; + Ushort = (ushort) val; + Byte = (byte) val; + Sbyte = (sbyte) val; + } + + [QuerySqlField] public double Double { get; set; } + [QuerySqlField] public float Float { get; set; } + [QuerySqlField] public decimal Decimal { get; set; } + [QuerySqlField] public int Int { get; set; } + [QuerySqlField] public uint Uint { get; set; } + [QuerySqlField] public long Long { get; set; } + [QuerySqlField] public ulong Ulong { get; set; } + [QuerySqlField] public short Short { get; set; } + [QuerySqlField] public ushort Ushort { get; set; } + [QuerySqlField] public byte Byte { get; set; } + [QuerySqlField] public sbyte Sbyte { get; set; } + } + + public struct RoleKey : IEquatable<RoleKey> + { + private readonly int _foo; + private readonly long _bar; + + public RoleKey(int foo, long bar) + { + _foo = foo; + _bar = bar; + } + + [QuerySqlField(Name = "_foo")] + public int Foo + { + get { return _foo; } + } + + [QuerySqlField(Name = "_bar")] + public long Bar + { + get { return _bar; } + } + + public bool Equals(RoleKey other) + { + return _foo == other._foo && _bar == other._bar; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is RoleKey && Equals((RoleKey) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (_foo*397) ^ _bar.GetHashCode(); + } + } + + public static bool operator ==(RoleKey left, RoleKey right) + { + return left.Equals(right); + } + + public static bool operator !=(RoleKey left, RoleKey right) + { + return !left.Equals(right); + } + } + + /// <summary> + /// Epsilon comparer. + /// </summary> + private class NumericComparer : IComparer + { + /** <inheritdoc /> */ + public int Compare(object x, object y) + { + if (Equals(x, y)) + return 0; + + if (x is double) + { + var dx = (double) x; + var dy = (double) y; + + // Epsilon is proportional to the min value, but not too small. + const double epsilon = 2E-10d; + var min = Math.Min(Math.Abs(dx), Math.Abs(dy)); + var relEpsilon = Math.Max(min*epsilon, epsilon); + + // Compare with epsilon because some funcs return slightly different results. + return Math.Abs((double) x - (double) y) < relEpsilon ? 0 : 1; + } + + return ((IComparable) x).CompareTo(y); + } + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestRunner.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestRunner.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestRunner.cs index a1083b6..74ea846 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestRunner.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/TestRunner.cs @@ -32,7 +32,7 @@ namespace Apache.Ignite.Core.Tests Debug.Listeners.Add(new TextWriterTraceListener(Console.Out)); Debug.AutoFlush = true; - TestOne(typeof(IgniteConfigurationTest), "TestStaticIpFinder"); + TestOne(typeof(CacheLinqTest), "TestExcept"); //TestAll(typeof (CacheQueriesCodeConfigurationTest)); //TestAllInAssembly(); http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/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 5ae7851..1be10b4 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj @@ -230,6 +230,7 @@ <Compile Include="Impl\Cache\Event\CacheEntryCreateEvent.cs" /> <Compile Include="Impl\Cache\Event\CacheEntryRemoveEvent.cs" /> <Compile Include="Impl\Cache\Event\CacheEntryUpdateEvent.cs" /> + <Compile Include="Impl\Cache\ICacheInternal.cs" /> <Compile Include="Impl\Cache\MutableCacheEntry.cs" /> <Compile Include="Impl\Cache\Query\AbstractQueryCursor.cs" /> <Compile Include="Impl\Cache\Query\Continuous\ContinuousQueryFilter.cs" /> @@ -266,6 +267,7 @@ <Compile Include="Impl\Common\IgniteConfigurationXmlSerializer.cs" /> <Compile Include="Impl\Common\IgniteHome.cs" /> <Compile Include="Impl\Common\LoadedAssembliesResolver.cs" /> + <Compile Include="Impl\Common\Logger.cs" /> <Compile Include="Impl\Common\ResizeableArray.cs" /> <Compile Include="Impl\Common\TypeCaster.cs" /> <Compile Include="Impl\Common\TypeStringConverter.cs" /> http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/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 f4c12f6..e6eceb8 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryEntity.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryEntity.cs @@ -104,7 +104,7 @@ namespace Apache.Ignite.Core.Cache.Configuration KeyTypeName = value == null ? null - : (JavaTypes.GetJavaTypeName(value) ?? BinaryUtils.GetTypeName(value)); + : (JavaTypes.GetJavaTypeNameAndLogWarning(value) ?? BinaryUtils.GetTypeName(value)); _keyType = value; } @@ -140,7 +140,7 @@ namespace Apache.Ignite.Core.Cache.Configuration ValueTypeName = value == null ? null - : (JavaTypes.GetJavaTypeName(value) ?? BinaryUtils.GetTypeName(value)); + : (JavaTypes.GetJavaTypeNameAndLogWarning(value) ?? BinaryUtils.GetTypeName(value)); _valueType = value; } http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryField.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryField.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryField.cs index 8c70a29..b11e53d 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryField.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Cache/Configuration/QueryField.cs @@ -87,7 +87,7 @@ namespace Apache.Ignite.Core.Cache.Configuration { FieldTypeName = value == null ? null - : (JavaTypes.GetJavaTypeName(value) ?? BinaryUtils.GetTypeName(value)); + : (JavaTypes.GetJavaTypeNameAndLogWarning(value) ?? BinaryUtils.GetTypeName(value)); _type = value; } http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs index b1693e7..94dab88 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Ignition.cs @@ -241,7 +241,7 @@ namespace Apache.Ignite.Core private static void CheckServerGc(IgniteConfiguration cfg) { if (!cfg.SuppressWarnings && !GCSettings.IsServerGC && Interlocked.CompareExchange(ref _gcWarn, 1, 0) == 0) - Console.WriteLine("GC server mode is not enabled, this could lead to less " + + Logger.LogWarning("GC server mode is not enabled, this could lead to less " + "than optimal performance on multi-core machines (to enable see " + "http://msdn.microsoft.com/en-us/library/ms229357(v=vs.110).aspx)."); } http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs index 8b5e2a1..07cf08f 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryReflectiveActions.cs @@ -20,9 +20,11 @@ namespace Apache.Ignite.Core.Impl.Binary using System; using System.Collections; using System.Diagnostics; + using System.Linq; using System.Linq.Expressions; using System.Reflection; using Apache.Ignite.Core.Binary; + using Apache.Ignite.Core.Cache.Configuration; using Apache.Ignite.Core.Common; using Apache.Ignite.Core.Impl.Common; @@ -398,6 +400,31 @@ namespace Apache.Ignite.Core.Impl.Binary } /// <summary> + /// Determines whether specified field is a query field (has QueryFieldAttribute). + /// </summary> + private static bool IsQueryField(FieldInfo fieldInfo) + { + Debug.Assert(fieldInfo != null && fieldInfo.DeclaringType != null); + + var fieldName = BinaryUtils.CleanFieldName(fieldInfo.Name); + + object[] attrs = null; + + if (fieldName != fieldInfo.Name) + { + // Backing field, check corresponding property + var prop = fieldInfo.DeclaringType.GetProperty(fieldName, fieldInfo.FieldType); + + if (prop != null) + attrs = prop.GetCustomAttributes(true); + } + + attrs = attrs ?? fieldInfo.GetCustomAttributes(true); + + return attrs.OfType<QuerySqlFieldAttribute>().Any(); + } + + /// <summary> /// Handle other type. /// </summary> /// <param name="field">The field.</param> @@ -476,6 +503,16 @@ namespace Apache.Ignite.Core.Impl.Binary ? GetRawReader(field, r => r.ReadCollection()) : GetReader(field, (f, r) => r.ReadCollection(f)); } + else if (type == typeof (DateTime) && IsQueryField(field)) + { + writeAction = GetWriter<DateTime>(field, (f, w, o) => w.WriteTimestamp(f, o)); + readAction = GetReader(field, (f, r) => r.ReadObject<DateTime>(f)); + } + else if (nullableType == typeof (DateTime) && IsQueryField(field)) + { + writeAction = GetWriter<DateTime?>(field, (f, w, o) => w.WriteTimestamp(f, o)); + readAction = GetReader(field, (f, r) => r.ReadTimestamp(f)); + } else { writeAction = raw ? GetRawWriter(field, MthdWriteObjRaw) : GetWriter(field, MthdWriteObj); http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/JavaTypes.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/JavaTypes.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/JavaTypes.cs index fccbfae..f30264d 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/JavaTypes.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/JavaTypes.cs @@ -20,6 +20,7 @@ namespace Apache.Ignite.Core.Impl.Binary using System; using System.Collections.Generic; using System.Linq; + using Apache.Ignite.Core.Impl.Common; /// <summary> /// Provides mapping between Java and .NET basic types. @@ -43,7 +44,18 @@ namespace Apache.Ignite.Core.Impl.Binary {typeof (double), "java.lang.Double"}, {typeof (string), "java.lang.String"}, {typeof (decimal), "java.math.BigDecimal"}, - {typeof (Guid), "java.util.UUID"} + {typeof (Guid), "java.util.UUID"}, + {typeof (DateTime), "java.sql.Timestamp"}, + {typeof (DateTime?), "java.sql.Timestamp"}, + }; + + /** */ + private static readonly Dictionary<Type, Type> IndirectMappingTypes = new Dictionary<Type, Type> + { + {typeof (sbyte), typeof (byte)}, + {typeof (ushort), typeof (short)}, + {typeof (uint), typeof (int)}, + {typeof (ulong), typeof (long)} }; /** */ @@ -55,15 +67,26 @@ namespace Apache.Ignite.Core.Impl.Binary /// <summary> /// Gets the corresponding Java type name. + /// Logs a warning for indirectly mapped types. /// </summary> - public static string GetJavaTypeName(Type type) + public static string GetJavaTypeNameAndLogWarning(Type type) { if (type == null) return null; string res; - return NetToJava.TryGetValue(type, out res) ? res : null; + if (!NetToJava.TryGetValue(type, out res)) + return null; + + Type directType; + + if (IndirectMappingTypes.TryGetValue(type, out directType)) + Logger.LogWarning("Type '{0}' maps to Java type '{1}' using unchecked conversion. " + + "This may cause issues in SQL queries. " + + "You can use '{2}' instead to achieve direct mapping.", type, res, directType); + + return res; } /// <summary> http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/CacheEntry.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/CacheEntry.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/CacheEntry.cs index 5537489..31bb2ed 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/CacheEntry.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/CacheEntry.cs @@ -24,7 +24,7 @@ namespace Apache.Ignite.Core.Impl.Cache /// <summary> /// Represents a cache entry. /// </summary> - internal struct CacheEntry<TK, TV> : ICacheEntry<TK, TV> + public struct CacheEntry<TK, TV> : ICacheEntry<TK, TV> { /** Key. */ private readonly TK _key; http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/CacheImpl.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/CacheImpl.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/CacheImpl.cs index 266012f..8a25a2c 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/CacheImpl.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/CacheImpl.cs @@ -41,7 +41,7 @@ namespace Apache.Ignite.Core.Impl.Cache /// Native cache wrapper. /// </summary> [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] - internal class CacheImpl<TK, TV> : PlatformTarget, ICache<TK, TV> + internal class CacheImpl<TK, TV> : PlatformTarget, ICache<TK, TV>, ICacheInternal { /** Duration: unchanged. */ private const long DurUnchanged = -2; @@ -940,7 +940,27 @@ namespace Apache.Ignite.Core.Impl.Cache /** <inheritDoc /> */ public IQueryCursor<IList> QueryFields(SqlFieldsQuery qry) { + return QueryFields(qry, ReadFieldsArrayList); + } + + /// <summary> + /// Reads the fields array list. + /// </summary> + private static IList ReadFieldsArrayList(IBinaryRawReader reader, int count) + { + IList res = new ArrayList(count); + + for (var i = 0; i < count; i++) + res.Add(reader.ReadObject<object>()); + + return res; + } + + /** <inheritDoc /> */ + public IQueryCursor<T> QueryFields<T>(SqlFieldsQuery qry, Func<IBinaryRawReader, int, T> readerFunc) + { IgniteArgumentCheck.NotNull(qry, "qry"); + IgniteArgumentCheck.NotNull(readerFunc, "readerFunc"); if (string.IsNullOrEmpty(qry.Sql)) throw new ArgumentException("Sql cannot be null or empty"); @@ -962,7 +982,7 @@ namespace Apache.Ignite.Core.Impl.Cache cursor = UU.CacheOutOpQueryCursor(Target, (int) CacheOp.QrySqlFields, stream.SynchronizeOutput()); } - return new FieldsQueryCursor(cursor, Marshaller, _flagKeepBinary); + return new FieldsQueryCursor<T>(cursor, Marshaller, _flagKeepBinary, readerFunc); } /** <inheritDoc /> */ @@ -998,9 +1018,18 @@ namespace Apache.Ignite.Core.Impl.Cache else { writer.WriteInt(args.Length); - + foreach (var arg in args) - writer.WriteObject(arg); + { + // Write DateTime as TimeStamp always, otherwise it does not make sense + // Wrapped DateTime comparison does not work in SQL + var dt = arg as DateTime?; // Works with DateTime also + + if (dt != null) + writer.WriteTimestamp(dt); + else + writer.WriteObject(arg); + } } } http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/ICacheInternal.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/ICacheInternal.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/ICacheInternal.cs new file mode 100644 index 0000000..a23cf08 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/ICacheInternal.cs @@ -0,0 +1,40 @@ +/* + * 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 +{ + using System; + using Apache.Ignite.Core.Binary; + using Apache.Ignite.Core.Cache.Query; + + /// <summary> + /// Extended Cache interface for internal needs. + /// </summary> + public interface ICacheInternal + { + /// <summary> + /// Queries separate entry fields. + /// </summary> + /// <typeparam name="T">Type of the result.</typeparam> + /// <param name="qry">SQL fields query.</param> + /// <param name="readerFunc">Reader function, takes raw reader and field count, returns typed result.</param> + /// <returns> + /// Cursor. + /// </returns> + IQueryCursor<T> QueryFields<T>(SqlFieldsQuery qry, Func<IBinaryRawReader, int, T> readerFunc); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/Query/FieldsQueryCursor.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/Query/FieldsQueryCursor.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/Query/FieldsQueryCursor.cs index 42fa1b9..d33fdce 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/Query/FieldsQueryCursor.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Cache/Query/FieldsQueryCursor.cs @@ -17,40 +17,44 @@ namespace Apache.Ignite.Core.Impl.Cache.Query { - using System.Collections; + using System; + using System.Diagnostics; using System.Diagnostics.CodeAnalysis; + using Apache.Ignite.Core.Binary; using Apache.Ignite.Core.Impl.Binary; using Apache.Ignite.Core.Impl.Unmanaged; /// <summary> /// Cursor for entry-based queries. /// </summary> - internal class FieldsQueryCursor : AbstractQueryCursor<IList> + internal class FieldsQueryCursor<T> : AbstractQueryCursor<T> { + /** */ + private readonly Func<IBinaryRawReader, int, T> _readerFunc; + /// <summary> /// Constructor. /// </summary> /// <param name="target">Target.</param> /// <param name="marsh">Marshaler.</param> /// <param name="keepBinary">Keep poratble flag.</param> - public FieldsQueryCursor(IUnmanagedTarget target, Marshaller marsh, bool keepBinary) + /// <param name="readerFunc">The reader function.</param> + public FieldsQueryCursor(IUnmanagedTarget target, Marshaller marsh, bool keepBinary, + Func<IBinaryRawReader, int, T> readerFunc) : base(target, marsh, keepBinary) { - // No-op. + Debug.Assert(readerFunc != null); + + _readerFunc = readerFunc; } /** <inheritdoc /> */ [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")] - protected override IList Read(BinaryReader reader) + protected override T Read(BinaryReader reader) { int cnt = reader.ReadInt(); - var res = new ArrayList(cnt); - - for (int i = 0; i < cnt; i++) - res.Add(reader.ReadObject<object>()); - - return res; + return _readerFunc(reader, cnt); } } }
