http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/DelegateConverter.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/DelegateConverter.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/DelegateConverter.cs index 00bda16..9a0f789 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/DelegateConverter.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/DelegateConverter.cs @@ -18,11 +18,13 @@ namespace Apache.Ignite.Core.Impl.Common { using System; + using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; + using Apache.Ignite.Core.Binary; /// <summary> /// Converts generic and non-generic delegates. @@ -32,6 +34,9 @@ namespace Apache.Ignite.Core.Impl.Common /** */ private const string DefaultMethodName = "Invoke"; + /** */ + private static readonly MethodInfo ReadObjectMethod = typeof (IBinaryRawReader).GetMethod("ReadObject"); + /// <summary> /// Compiles a function without arguments. /// </summary> @@ -188,7 +193,7 @@ namespace Apache.Ignite.Core.Impl.Common /// <typeparam name="T">Result func type.</typeparam> /// <param name="type">Type to be created by ctor.</param> /// <param name="argTypes">Argument types.</param> - /// <param name="convertResultToObject">if set to <c>true</c> [convert result to object]. + /// <param name="convertResultToObject"> /// Flag that indicates whether ctor return value should be converted to object. /// </param> /// <returns> @@ -203,6 +208,73 @@ namespace Apache.Ignite.Core.Impl.Common } /// <summary> + /// Compiles a contructor that reads all arguments from a binary reader. + /// </summary> + /// <typeparam name="T">Result type</typeparam> + /// <param name="ctor">The ctor.</param> + /// <param name="innerCtorFunc">Function to retrieve reading constructor for an argument. + /// Can be null or return null, in this case the argument will be read directly via ReadObject.</param> + /// <returns></returns> + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")] + public static Func<IBinaryRawReader, T> CompileCtor<T>(ConstructorInfo ctor, + Func<Type, ConstructorInfo> innerCtorFunc) + { + Debug.Assert(ctor != null); + + var readerParam = Expression.Parameter(typeof (IBinaryRawReader)); + + var ctorExpr = GetConstructorExpression(ctor, innerCtorFunc, readerParam, typeof(T)); + + return Expression.Lambda<Func<IBinaryRawReader, T>>(ctorExpr, readerParam).Compile(); + } + + /// <summary> + /// Gets the constructor expression. + /// </summary> + /// <param name="ctor">The ctor.</param> + /// <param name="innerCtorFunc">The inner ctor function.</param> + /// <param name="readerParam">The reader parameter.</param> + /// <param name="resultType">Type of the result.</param> + /// <returns> + /// Ctor call expression. + /// </returns> + private static Expression GetConstructorExpression(ConstructorInfo ctor, + Func<Type, ConstructorInfo> innerCtorFunc, Expression readerParam, Type resultType) + { + var ctorParams = ctor.GetParameters(); + + var paramsExpr = new List<Expression>(ctorParams.Length); + + foreach (var param in ctorParams) + { + var paramType = param.ParameterType; + + var innerCtor = innerCtorFunc != null ? innerCtorFunc(paramType) : null; + + if (innerCtor != null) + { + var readExpr = GetConstructorExpression(innerCtor, innerCtorFunc, readerParam, paramType); + + paramsExpr.Add(readExpr); + } + else + { + var readMethod = ReadObjectMethod.MakeGenericMethod(paramType); + + var readExpr = Expression.Call(readerParam, readMethod); + + paramsExpr.Add(readExpr); + } + } + + Expression ctorExpr = Expression.New(ctor, paramsExpr); + + ctorExpr = Expression.Convert(ctorExpr, resultType); + + return ctorExpr; + } + + /// <summary> /// Compiles the field setter. /// </summary> /// <param name="field">The field.</param> @@ -211,7 +283,7 @@ namespace Apache.Ignite.Core.Impl.Common public static Action<object, object> CompileFieldSetter(FieldInfo field) { Debug.Assert(field != null); - Debug.Assert(field.DeclaringType != null); // non-static + Debug.Assert(field.DeclaringType != null); var targetParam = Expression.Parameter(typeof(object)); var valParam = Expression.Parameter(typeof(object)); @@ -231,7 +303,7 @@ namespace Apache.Ignite.Core.Impl.Common public static Action<object, object> CompilePropertySetter(PropertyInfo prop) { Debug.Assert(prop != null); - Debug.Assert(prop.DeclaringType != null); // non-static + Debug.Assert(prop.DeclaringType != null); var targetParam = Expression.Parameter(typeof(object)); var targetParamConverted = Expression.Convert(targetParam, prop.DeclaringType); @@ -247,6 +319,53 @@ namespace Apache.Ignite.Core.Impl.Common } /// <summary> + /// Compiles the property setter. + /// </summary> + /// <param name="prop">The property.</param> + /// <returns>Compiled property setter.</returns> + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")] + public static Func<object, object> CompilePropertyGetter(PropertyInfo prop) + { + Debug.Assert(prop != null); + Debug.Assert(prop.DeclaringType != null); + + var targetParam = Expression.Parameter(typeof(object)); + var targetParamConverted = prop.GetGetMethod().IsStatic + ? null + // ReSharper disable once AssignNullToNotNullAttribute (incorrect warning) + : Expression.Convert(targetParam, prop.DeclaringType); + + var fld = Expression.Property(targetParamConverted, prop); + + var fldConverted = Expression.Convert(fld, typeof (object)); + + return Expression.Lambda<Func<object, object>>(fldConverted, targetParam).Compile(); + } + + /// <summary> + /// Compiles the property setter. + /// </summary> + /// <param name="field">The field.</param> + /// <returns>Compiled property setter.</returns> + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")] + public static Func<object, object> CompileFieldGetter(FieldInfo field) + { + Debug.Assert(field != null); + Debug.Assert(field.DeclaringType != null); + + var targetParam = Expression.Parameter(typeof(object)); + var targetParamConverted = field.IsStatic + ? null + : Expression.Convert(targetParam, field.DeclaringType); + + var fld = Expression.Field(targetParamConverted, field); + + var fldConverted = Expression.Convert(fld, typeof (object)); + + return Expression.Lambda<Func<object, object>>(fldConverted, targetParam).Compile(); + } + + /// <summary> /// Gets a method to write a field (including private and readonly). /// NOTE: Expression Trees can't write readonly fields. /// </summary> @@ -259,7 +378,7 @@ namespace Apache.Ignite.Core.Impl.Common var declaringType = field.DeclaringType; - Debug.Assert(declaringType != null); // static fields are not supported + Debug.Assert(declaringType != null); var method = new DynamicMethod(string.Empty, null, new[] { typeof(object), field.FieldType }, declaringType, true);
http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/Logger.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/Logger.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/Logger.cs new file mode 100644 index 0000000..cab5afc --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/Logger.cs @@ -0,0 +1,37 @@ +/* +* 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.Common +{ + using System; + + /// <summary> + /// Console logger. + /// </summary> + internal static class Logger + { + /// <summary> + /// Logs the warning. + /// </summary> + /// <param name="warning">The warning.</param> + /// <param name="args">The arguments.</param> + public static void LogWarning(string warning, params object[] args) + { + Console.WriteLine("WARNING: " + string.Format(warning, args)); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/TypeCaster.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/TypeCaster.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/TypeCaster.cs index 2d4936f..6e6bf7d 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/TypeCaster.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Common/TypeCaster.cs @@ -39,7 +39,19 @@ namespace Apache.Ignite.Core.Impl.Common Justification = "Intended usage to leverage compiler caching.")] public static T Cast<TFrom>(TFrom obj) { +#if (DEBUG) + try + { + return Casters<TFrom>.Caster(obj); + } + catch (InvalidCastException) + { + throw new InvalidCastException(string.Format("Specified cast is not valid: {0} -> {1}", typeof (TFrom), + typeof (T))); + } +#else return Casters<TFrom>.Caster(obj); +#endif } /// <summary> http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteManager.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteManager.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteManager.cs index b35c46f..7bd0417 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteManager.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/IgniteManager.cs @@ -69,7 +69,7 @@ namespace Apache.Ignite.Core.Impl { if (!_jvmCfg.Equals(jvmCfg)) { - Console.WriteLine("Attempting to start Ignite node with different Java " + + Logger.LogWarning("Attempting to start Ignite node with different Java " + "configuration; current Java configuration will be ignored (consider " + "starting node in separate process) [oldConfig=" + _jvmCfg + ", newConfig=" + jvmCfg + ']'); http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.csproj ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.csproj b/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.csproj new file mode 100644 index 0000000..59aef1c --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.csproj @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> + <PropertyGroup> + <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> + <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> + <ProjectGuid>{5B571661-17F4-4F29-8C7D-0EDB38CA9B55}</ProjectGuid> + <OutputType>Library</OutputType> + <AppDesignerFolder>Properties</AppDesignerFolder> + <RootNamespace>Apache.Ignite.Linq</RootNamespace> + <AssemblyName>Apache.Ignite.Linq</AssemblyName> + <TargetFrameworkVersion>v4.0</TargetFrameworkVersion> + <FileAlignment>512</FileAlignment> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'"> + <DebugSymbols>true</DebugSymbols> + <OutputPath>bin\Debug\</OutputPath> + <DefineConstants>DEBUG;TRACE</DefineConstants> + <DebugType>full</DebugType> + <PlatformTarget>AnyCPU</PlatformTarget> + <ErrorReport>prompt</ErrorReport> + <CodeAnalysisRuleSet>..\Apache.Ignite.Core\Apache.Ignite.Core.ruleset</CodeAnalysisRuleSet> + <RunCodeAnalysis>true</RunCodeAnalysis> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'"> + <OutputPath>bin\Release\</OutputPath> + <DefineConstants>TRACE</DefineConstants> + <Optimize>true</Optimize> + <DebugType>none</DebugType> + <PlatformTarget>AnyCPU</PlatformTarget> + <ErrorReport>prompt</ErrorReport> + <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> + </PropertyGroup> + <PropertyGroup> + <SignAssembly>true</SignAssembly> + </PropertyGroup> + <PropertyGroup> + <AssemblyOriginatorKeyFile>Apache.Ignite.Linq.snk</AssemblyOriginatorKeyFile> + </PropertyGroup> + <ItemGroup> + <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" /> + <Reference Include="System.Xml.Linq" /> + <Reference Include="System.Data.DataSetExtensions" /> + <Reference Include="Microsoft.CSharp" /> + <Reference Include="System.Data" /> + <Reference Include="System.Xml" /> + </ItemGroup> + <ItemGroup> + <Compile Include="CacheExtensions.cs" /> + <Compile Include="CompiledQuery.cs" /> + <Compile Include="ICacheQueryable.cs" /> + <Compile Include="Impl\AliasDictionary.cs" /> + <Compile Include="Impl\CacheFieldsQueryable.cs" /> + <Compile Include="Impl\CacheFieldsQueryExecutor.cs" /> + <Compile Include="Impl\CacheFieldsQueryProvider.cs" /> + <Compile Include="Impl\CacheQueryable.cs" /> + <Compile Include="Impl\CacheQueryableBase.cs" /> + <Compile Include="Impl\CacheQueryExpressionVisitor.cs" /> + <Compile Include="Impl\CacheQueryModelVisitor.cs" /> + <Compile Include="Impl\CacheQueryParser.cs" /> + <Compile Include="Impl\ICacheQueryableInternal.cs" /> + <Compile Include="Impl\ICacheQueryProxy.cs" /> + <Compile Include="Impl\MethodVisitor.cs" /> + <Compile Include="Impl\QueryData.cs" /> + <Compile Include="Impl\SqlTypes.cs" /> + <Compile Include="Impl\ExpressionWalker.cs" /> + <Compile Include="Properties\AssemblyInfo.cs" /> + </ItemGroup> + <ItemGroup> + <None Include="Apache.Ignite.Linq.nuspec" /> + <None Include="Apache.Ignite.Linq.snk" /> + <None Include="packages.config" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\Apache.Ignite.Core\Apache.Ignite.Core.csproj"> + <Project>{4cd2f726-7e2b-46c4-a5ba-057bb82eecb6}</Project> + <Name>Apache.Ignite.Core</Name> + </ProjectReference> + </ItemGroup> + <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> + <!-- 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. + <Target Name="BeforeBuild"> + </Target> + <Target Name="AfterBuild"> + </Target> + --> +</Project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.nuspec ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.nuspec b/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.nuspec new file mode 100644 index 0000000..7654dac --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.nuspec @@ -0,0 +1,63 @@ +<?xml version="1.0"?> + +<!-- + 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. +--> + +<!-- + +Creating NuGet package: +1) Build Apache.Ignite.sln (x64 configuration) +2) Create package (use csproj instead of nuspec so that template substitution works): + nuget pack Apache.Ignite.Linq.csproj -Prop Configuration=Release -Prop Platform=x64 + +--> + +<package > + <metadata> + <id>Apache.Ignite.Linq</id> + <title>Apache Ignite LINQ Provider</title> + <!-- --> + <version>$version$</version> + <authors>Apache Ignite</authors> + <owners>Apache Software Foundation</owners> + <licenseUrl>http://www.apache.org/licenses/LICENSE-2.0</licenseUrl> + <projectUrl>https://ignite.apache.org/</projectUrl> + <iconUrl>https://ignite.apache.org/images/logo_ignite_32_32.png</iconUrl> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <description> +LINQ Provider for Apache Ignite + +More info: https://apacheignite-net.readme.io/ + +WARNING: this only works with x64 build targets. + </description> + <summary> + LINQ Provider for Apache Ignite + </summary> + <releaseNotes></releaseNotes> + <copyright>Copyright 2015</copyright> + <tags>Apache Ignite In-Memory Distributed Computing SQL NoSQL LINQ Grid Map Reduce Cache linqpad-samples</tags> + <dependencies> + <dependency id="Apache.Ignite" version="[$version$]" /> + <dependency id="Remotion.Linq" version="[2.0.1]" /> + </dependencies> + </metadata> + <files> + <!-- LINQPad samples --> + <file src="NuGet\LINQPad\*.*" target="linqpad-samples" /> + </files> +</package> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.snk ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.snk b/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.snk new file mode 100644 index 0000000..799e742 Binary files /dev/null and b/modules/platforms/dotnet/Apache.Ignite.Linq/Apache.Ignite.Linq.snk differ http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/CacheExtensions.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/CacheExtensions.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/CacheExtensions.cs new file mode 100644 index 0000000..ecea4ed --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Linq/CacheExtensions.cs @@ -0,0 +1,98 @@ +/* + * 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.Linq +{ + using System.Linq; + using Apache.Ignite.Core.Cache; + using Apache.Ignite.Core.Cache.Configuration; + using Apache.Ignite.Linq.Impl; + + /// <summary> + /// Extensions methods for <see cref="ICache{TK,TV}"/>. + /// </summary> + public static class CacheLinqExtensions + { + /// <summary> + /// Gets an <see cref="IQueryable{T}"/> instance over this cache. + /// <para /> + /// Resulting query will be translated to cache SQL query and executed over the cache instance + /// via either <see cref="ICache{TK,TV}.Query"/> or <see cref="ICache{TK,TV}.QueryFields"/>, + /// depending on requested result. + /// <para /> + /// Result of this method (and subsequent query) can be cast to <see cref="ICacheQueryable"/> for introspection. + /// </summary> + /// <typeparam name="TKey">The type of the key.</typeparam> + /// <typeparam name="TValue">The type of the value.</typeparam> + /// <param name="cache">The cache.</param> + /// <returns><see cref="IQueryable{T}"/> instance over this cache.</returns> + public static IQueryable<ICacheEntry<TKey, TValue>> AsCacheQueryable<TKey, TValue>( + this ICache<TKey, TValue> cache) + { + return cache.AsCacheQueryable(false, null); + } + + /// <summary> + /// Gets an <see cref="IQueryable{T}"/> instance over this cache. + /// <para /> + /// Resulting query will be translated to cache SQL query and executed over the cache instance + /// via either <see cref="ICache{TK,TV}.Query"/> or <see cref="ICache{TK,TV}.QueryFields"/>, + /// depending on requested result. + /// <para /> + /// Result of this method (and subsequent query) can be cast to <see cref="ICacheQueryable"/> for introspection. + /// </summary> + /// <typeparam name="TKey">The type of the key.</typeparam> + /// <typeparam name="TValue">The type of the value.</typeparam> + /// <param name="cache">The cache.</param> + /// <param name="local">Local flag. When set query will be executed only on local node, so only local + /// entries will be returned as query result.</param> + /// <returns><see cref="IQueryable{T}"/> instance over this cache.</returns> + public static IQueryable<ICacheEntry<TKey, TValue>> AsCacheQueryable<TKey, TValue>( + this ICache<TKey, TValue> cache, bool local) + { + return cache.AsCacheQueryable(local, null); + } + + /// <summary> + /// Gets an <see cref="IQueryable{T}" /> instance over this cache. + /// <para /> + /// Resulting query will be translated to cache SQL query and executed over the cache instance + /// via either <see cref="ICache{TK,TV}.Query" /> or <see cref="ICache{TK,TV}.QueryFields" />, + /// depending on requested result. + /// <para /> + /// Result of this method (and subsequent query) can be cast to <see cref="ICacheQueryable" /> for introspection. + /// </summary> + /// <typeparam name="TKey">The type of the key.</typeparam> + /// <typeparam name="TValue">The type of the value.</typeparam> + /// <param name="cache">The cache.</param> + /// <param name="local">Local flag. When set query will be executed only on local node, so only local + /// entries will be returned as query result.</param> + /// <param name="tableName"> + /// Name of the table. + /// <para /> + /// Table name is equal to short class name of a cache value. + /// When a cache has only one type of values, or only one <see cref="QueryEntity"/> defined, + /// table name will be inferred and can be omitted. + /// </param> + /// <returns><see cref="IQueryable{T}" /> instance over this cache.</returns> + public static IQueryable<ICacheEntry<TKey, TValue>> AsCacheQueryable<TKey, TValue>( + this ICache<TKey, TValue> cache, bool local, string tableName) + { + return new CacheQueryable<TKey, TValue>(cache, local, tableName); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/CompiledQuery.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/CompiledQuery.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/CompiledQuery.cs new file mode 100644 index 0000000..2fa66ce --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Linq/CompiledQuery.cs @@ -0,0 +1,206 @@ +/* + * 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.Linq +{ + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using Apache.Ignite.Core.Cache.Query; + using Apache.Ignite.Core.Impl.Common; + using Apache.Ignite.Linq.Impl; + + /// <summary> + /// Represents a compiled cache query. + /// </summary> + public static class CompiledQuery + { + /// <summary> + /// Creates a new delegate that represents the compiled cache query. + /// </summary> + /// <param name="query">The query to compile.</param> + /// <returns>Delegate that represents the compiled cache query.</returns> + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", + Justification = "Invalid warning, validation is present.")] + public static Func<IQueryCursor<T>> Compile<T>(Func<IQueryable<T>> query) + { + IgniteArgumentCheck.NotNull(query, "query"); + + var compiledQuery = GetCompiledQuery(query(), null); + + return () => compiledQuery(new object[0]); + } + + /// <summary> + /// Creates a new delegate that represents the compiled cache query. + /// </summary> + /// <param name="query">The query to compile.</param> + /// <returns>Delegate that represents the compiled cache query.</returns> + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", + Justification = "Invalid warning, validation is present.")] + public static Func<T1, IQueryCursor<T>> Compile<T, T1>(Func<T1, IQueryable<T>> query) + { + IgniteArgumentCheck.NotNull(query, "query"); + + var compiledQuery = GetCompiledQuery(query(default(T1)), null); + + return x => compiledQuery(new object[] {x}); + } + + /// <summary> + /// Creates a new delegate that represents the compiled cache query. + /// </summary> + /// <param name="query">The query to compile.</param> + /// <returns>Delegate that represents the compiled cache query.</returns> + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", + Justification = "Invalid warning, validation is present.")] + public static Func<T1, T2, IQueryCursor<T>> Compile<T, T1, T2>(Func<T1, T2, + IQueryable<T>> query) + { + IgniteArgumentCheck.NotNull(query, "query"); + + var compiledQuery = GetCompiledQuery(query(default(T1), default(T2)), query); + + return (x, y) => compiledQuery(new object[] {x, y}); + } + + /// <summary> + /// Creates a new delegate that represents the compiled cache query. + /// </summary> + /// <param name="query">The query to compile.</param> + /// <returns>Delegate that represents the compiled cache query.</returns> + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", + Justification = "Invalid warning, validation is present.")] + public static Func<T1, T2, T3, IQueryCursor<T>> Compile<T, T1, T2, T3>(Func<T1, T2, T3, + IQueryable<T>> query) + { + IgniteArgumentCheck.NotNull(query, "query"); + + var compiledQuery = GetCompiledQuery(query(default(T1), default(T2), default(T3)), query); + + return (x, y, z) => compiledQuery(new object[] {x, y, z}); + } + + /// <summary> + /// Creates a new delegate that represents the compiled cache query. + /// </summary> + /// <param name="query">The query to compile.</param> + /// <returns>Delegate that represents the compiled cache query.</returns> + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", + Justification = "Invalid warning, validation is present.")] + public static Func<T1, T2, T3, T4, IQueryCursor<T>> Compile<T, T1, T2, T3, T4>(Func<T1, T2, T3, T4, + IQueryable<T>> query) + { + IgniteArgumentCheck.NotNull(query, "query"); + + var compiledQuery = GetCompiledQuery(query(default(T1), default(T2), default(T3), default(T4)), query); + + return (x, y, z, a) => compiledQuery(new object[] {x, y, z, a}); + } + + /// <summary> + /// Creates a new delegate that represents the compiled cache query. + /// </summary> + /// <param name="query">The query to compile.</param> + /// <returns>Delegate that represents the compiled cache query.</returns> + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", + Justification = "Invalid warning, validation is present.")] + public static Func<T1, T2, T3, T4, T5, IQueryCursor<T>> Compile<T, T1, T2, T3, T4, T5>( + Func<T1, T2, T3, T4, T5, IQueryable<T>> query) + { + IgniteArgumentCheck.NotNull(query, "query"); + + var compiledQuery = + GetCompiledQuery(query(default(T1), default(T2), default(T3), default(T4), default(T5)), query); + + return (x, y, z, a, b) => compiledQuery(new object[] {x, y, z, a, b}); + } + + /// <summary> + /// Creates a new delegate that represents the compiled cache query. + /// </summary> + /// <param name="query">The query to compile.</param> + /// <returns>Delegate that represents the compiled cache query.</returns> + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", + Justification = "Invalid warning, validation is present.")] + public static Func<T1, T2, T3, T4, T5, T6, IQueryCursor<T>> Compile<T, T1, T2, T3, T4, T5, T6>( + Func<T1, T2, T3, T4, T5, T6, IQueryable<T>> query) + { + IgniteArgumentCheck.NotNull(query, "query"); + + var compiledQuery = GetCompiledQuery(query(default(T1), default(T2), default(T3), default(T4), + default(T5), default(T6)), query); + + return (x, y, z, a, b, c) => compiledQuery(new object[] {x, y, z, a, b, c}); + } + + /// <summary> + /// Creates a new delegate that represents the compiled cache query. + /// </summary> + /// <param name="query">The query to compile.</param> + /// <returns>Delegate that represents the compiled cache query.</returns> + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", + Justification = "Invalid warning, validation is present.")] + public static Func<T1, T2, T3, T4, T5, T6, T7, IQueryCursor<T>> Compile<T, T1, T2, T3, T4, T5, T6, T7>( + Func<T1, T2, T3, T4, T5, T6, T7, IQueryable<T>> query) + { + IgniteArgumentCheck.NotNull(query, "query"); + + var compiledQuery = GetCompiledQuery(query(default(T1), default(T2), default(T3), default(T4), + default(T5), default(T6), default(T7)), query); + + return (x, y, z, a, b, c, d) => compiledQuery(new object[] {x, y, z, a, b, c, d}); + } + + /// <summary> + /// Creates a new delegate that represents the compiled cache query. + /// </summary> + /// <param name="query">The query to compile.</param> + /// <returns>Delegate that represents the compiled cache query.</returns> + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", + Justification = "Invalid warning, validation is present.")] + public static Func<T1, T2, T3, T4, T5, T6, T7, T8, IQueryCursor<T>> Compile<T, T1, T2, T3, T4, T5, T6, T7, T8>( + Func<T1, T2, T3, T4, T5, T6, T7, T8, IQueryable<T>> query) + { + IgniteArgumentCheck.NotNull(query, "query"); + + var compiledQuery = GetCompiledQuery(query(default(T1), default(T2), default(T3), default(T4), + default(T5), default(T6), default(T7), default(T8)), query); + + return (x, y, z, a, b, c, d, e) => compiledQuery(new object[] {x, y, z, a, b, c, d, e}); + } + + /// <summary> + /// Gets the compiled query. + /// </summary> + private static Func<object[], IQueryCursor<T>> GetCompiledQuery<T>(IQueryable<T> queryable, + Delegate queryCaller) + { + var cacheQueryable = queryable as ICacheQueryableInternal; + + if (cacheQueryable == null) + throw new ArgumentException( + string.Format("{0} can only compile cache queries produced by AsCacheQueryable method. " + + "Provided query is not valid: '{1}'", typeof (CompiledQuery).FullName, queryable)); + + Debug.WriteLine(queryable); + + return cacheQueryable.CompileQuery<T>(queryCaller); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/ICacheQueryable.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/ICacheQueryable.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/ICacheQueryable.cs new file mode 100644 index 0000000..684f746 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Linq/ICacheQueryable.cs @@ -0,0 +1,53 @@ +/* + * 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.Linq +{ + using System; + using Apache.Ignite.Core; + using Apache.Ignite.Core.Cache.Query; + + /// <summary> + /// Common interface for cache queryables. + /// </summary> + public interface ICacheQueryable + { + /// <summary> + /// Gets the name of the cache that is associated with this query. + /// </summary> + /// <value> + /// The name of the cache. + /// </value> + string CacheName { get; } + + /// <summary> + /// Gets the Ignite instance associated with this query. + /// </summary> + IIgnite Ignite { get; } + + /// <summary> + /// Returns fields query that represents current queryable. + /// </summary> + /// <returns>Fields query that represents current queryable.</returns> + SqlFieldsQuery GetFieldsQuery(); + + /// <summary> + /// Gets the type of the element. + /// </summary> + Type ElementType { get; } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/AliasDictionary.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/AliasDictionary.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/AliasDictionary.cs new file mode 100644 index 0000000..10a414c --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/AliasDictionary.cs @@ -0,0 +1,102 @@ +/* + * 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.Linq.Impl +{ + using System.Collections.Generic; + using System.Diagnostics; + using System.Text; + using Remotion.Linq.Clauses; + + /// <summary> + /// Alias dictionary. + /// </summary> + internal class AliasDictionary + { + /** */ + private int _aliasIndex; + + /** */ + private Dictionary<string, string> _aliases = new Dictionary<string, string>(); + + /** */ + private readonly Stack<Dictionary<string, string>> _stack = new Stack<Dictionary<string, string>>(); + + /// <summary> + /// Pushes current aliases to stack. + /// </summary> + public void Push() + { + _stack.Push(_aliases); + + _aliases = new Dictionary<string, string>(); + } + + /// <summary> + /// Pops current aliases from stack. + /// </summary> + public void Pop() + { + _aliases = _stack.Pop(); + } + + /// <summary> + /// Gets the table alias. + /// </summary> + public string GetTableAlias(ICacheQueryableInternal queryable) + { + Debug.Assert(queryable != null); + + return GetTableAlias(ExpressionWalker.GetTableNameWithSchema(queryable)); + } + + /// <summary> + /// Gets the table alias. + /// </summary> + public string GetTableAlias(string fullName) + { + Debug.Assert(!string.IsNullOrEmpty(fullName)); + + string alias; + + if (!_aliases.TryGetValue(fullName, out alias)) + { + alias = "_T" + _aliasIndex++; + + _aliases[fullName] = alias; + } + + return alias; + } + + /// <summary> + /// Appends as clause. + /// </summary> + public StringBuilder AppendAsClause(StringBuilder builder, IFromClause clause) + { + Debug.Assert(builder != null); + Debug.Assert(clause != null); + + var queryable = ExpressionWalker.GetCacheQueryable(clause); + var tableName = ExpressionWalker.GetTableNameWithSchema(queryable); + + builder.AppendFormat("{0} as {1}", tableName, GetTableAlias(tableName)); + + return builder; + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryExecutor.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryExecutor.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryExecutor.cs new file mode 100644 index 0000000..09f57ff --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryExecutor.cs @@ -0,0 +1,225 @@ +/* + * 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.Linq.Impl +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using Apache.Ignite.Core.Binary; + using Apache.Ignite.Core.Cache; + using Apache.Ignite.Core.Cache.Query; + using Apache.Ignite.Core.Impl.Cache; + using Apache.Ignite.Core.Impl.Common; + using Remotion.Linq; + + /// <summary> + /// Fields query executor. + /// </summary> + internal class CacheFieldsQueryExecutor : IQueryExecutor + { + /** */ + private readonly ICacheInternal _cache; + + /** */ + private static readonly CopyOnWriteConcurrentDictionary<ConstructorInfo, object> CtorCache = + new CopyOnWriteConcurrentDictionary<ConstructorInfo, object>(); + + /** */ + private readonly bool _local; + + /// <summary> + /// Initializes a new instance of the <see cref="CacheFieldsQueryExecutor" /> class. + /// </summary> + /// <param name="cache">The executor function.</param> + /// <param name="local">Local flag.</param> + public CacheFieldsQueryExecutor(ICacheInternal cache, bool local) + { + Debug.Assert(cache != null); + + _cache = cache; + _local = local; + } + + /// <summary> + /// Gets the local flag. + /// </summary> + public bool Local + { + get { return _local; } + } + + /** <inheritdoc /> */ + public T ExecuteScalar<T>(QueryModel queryModel) + { + return ExecuteSingle<T>(queryModel, false); + } + + /** <inheritdoc /> */ + public T ExecuteSingle<T>(QueryModel queryModel, bool returnDefaultWhenEmpty) + { + var col = ExecuteCollection<T>(queryModel); + + return returnDefaultWhenEmpty ? col.SingleOrDefault() : col.Single(); + } + + /** <inheritdoc /> */ + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")] + public IEnumerable<T> ExecuteCollection<T>(QueryModel queryModel) + { + Debug.Assert(queryModel != null); + + var qryData = GetQueryData(queryModel); + + Debug.WriteLine("\nFields Query: {0} | {1}", qryData.QueryText, + string.Join(", ", qryData.Parameters.Select(x => x == null ? "null" : x.ToString()))); + + var qry = new SqlFieldsQuery(qryData.QueryText, _local, qryData.Parameters.ToArray()); + + var selector = GetResultSelector<T>(queryModel.SelectClause.Selector); + + return _cache.QueryFields(qry, selector); + } + + /// <summary> + /// Compiles the query. + /// </summary> + public Func<object[], IQueryCursor<T>> CompileQuery<T>(QueryModel queryModel, Delegate queryCaller) + { + Debug.Assert(queryModel != null); + + var qryData = GetQueryData(queryModel); + + var qryText = qryData.QueryText; + + var selector = GetResultSelector<T>(queryModel.SelectClause.Selector); + + if (queryCaller == null) + return args => _cache.QueryFields(new SqlFieldsQuery(qryText, _local, args), selector); + + // Compiled query is a delegate with query parameters + // Delegate parameters order and query parameters order may differ + + // These are in order of usage in query + var qryOrderParams = qryData.ParameterExpressions.OfType<MemberExpression>() + .Select(x => x.Member.Name).ToList(); + + // These are in order they come from user + var userOrderParams = queryCaller.Method.GetParameters().Select(x => x.Name).ToList(); + + if ((qryOrderParams.Count != qryData.Parameters.Count) || + (qryOrderParams.Count != userOrderParams.Count)) + throw new InvalidOperationException("Error compiling query: all compiled query arguments " + + "should come from enclosing lambda expression"); + + var indices = qryOrderParams.Select(x => userOrderParams.IndexOf(x)).ToArray(); + + // Check if user param order is already correct + if (indices.SequenceEqual(Enumerable.Range(0, indices.Length))) + return args => _cache.QueryFields(new SqlFieldsQuery(qryText, _local, args), selector); + + // Return delegate with reorder + return args => _cache.QueryFields(new SqlFieldsQuery(qryText, _local, + args.Select((x, i) => args[indices[i]]).ToArray()), selector); + } + + /** <inheritdoc /> */ + public static QueryData GetQueryData(QueryModel queryModel) + { + Debug.Assert(queryModel != null); + + return new CacheQueryModelVisitor().GenerateQuery(queryModel); + } + + /// <summary> + /// Gets the result selector. + /// </summary> + private static Func<IBinaryRawReader, int, T> GetResultSelector<T>(Expression selectorExpression) + { + var newExpr = selectorExpression as NewExpression; + + if (newExpr != null) + return GetCompiledCtor<T>(newExpr.Constructor); + + var entryCtor = GetCacheEntryCtorInfo(typeof(T)); + + if (entryCtor != null) + return GetCompiledCtor<T>(entryCtor); + + if (typeof(T) == typeof(bool)) + return ReadBool<T>; + + return (reader, count) => reader.ReadObject<T>(); + } + + /// <summary> + /// Reads the bool. Actual data may be bool or int/long. + /// </summary> + private static T ReadBool<T>(IBinaryRawReader reader, int count) + { + var obj = reader.ReadObject<object>(); + + if (obj is bool) + return (T) obj; + + if (obj is long) + return TypeCaster<T>.Cast((long) obj != 0); + + if (obj is int) + return TypeCaster<T>.Cast((int) obj != 0); + + throw new InvalidOperationException("Expected bool, got: " + obj); + } + + /// <summary> + /// Gets the cache entry constructor. + /// </summary> + private static ConstructorInfo GetCacheEntryCtorInfo(Type entryType) + { + if (!entryType.IsGenericType || entryType.GetGenericTypeDefinition() != typeof(ICacheEntry<,>)) + return null; + + var args = entryType.GetGenericArguments(); + + var targetType = typeof (CacheEntry<,>).MakeGenericType(args); + + return targetType.GetConstructors().Single(); + } + + /// <summary> + /// Gets the compiled constructor. + /// </summary> + private static Func<IBinaryRawReader, int, T> GetCompiledCtor<T>(ConstructorInfo ctorInfo) + { + object result; + + if (CtorCache.TryGetValue(ctorInfo, out result)) + return (Func<IBinaryRawReader, int, T>) result; + + return (Func<IBinaryRawReader, int, T>) CtorCache.GetOrAdd(ctorInfo, x => + { + var innerCtor1 = DelegateConverter.CompileCtor<T>(x, GetCacheEntryCtorInfo); + + return (Func<IBinaryRawReader, int, T>) ((r, c) => innerCtor1(r)); + }); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/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 new file mode 100644 index 0000000..3f5fe34 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryProvider.cs @@ -0,0 +1,239 @@ +/* + * 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.Linq.Impl +{ + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using Apache.Ignite.Core; + using Apache.Ignite.Core.Cache; + using Apache.Ignite.Core.Cache.Configuration; + using Remotion.Linq; + using Remotion.Linq.Clauses.StreamedData; + using Remotion.Linq.Parsing.Structure; + using Remotion.Linq.Utilities; + + /// <summary> + /// Query provider for fields queries (projections). + /// </summary> + internal class CacheFieldsQueryProvider : IQueryProvider + { + /** */ + private static readonly MethodInfo GenericCreateQueryMethod = + typeof (CacheFieldsQueryProvider).GetMethods().Single(m => m.Name == "CreateQuery" && m.IsGenericMethod); + + /** */ + private readonly IQueryParser _parser; + + /** */ + private readonly CacheFieldsQueryExecutor _executor; + + /** */ + private readonly IIgnite _ignite; + + /** */ + private readonly CacheConfiguration _cacheConfiguration; + + /** */ + private readonly string _tableName; + + /// <summary> + /// Initializes a new instance of the <see cref="CacheFieldsQueryProvider"/> class. + /// </summary> + public CacheFieldsQueryProvider(IQueryParser queryParser, CacheFieldsQueryExecutor executor, IIgnite ignite, + CacheConfiguration cacheConfiguration, string tableName, Type cacheValueType) + { + Debug.Assert(queryParser != null); + Debug.Assert(executor != null); + Debug.Assert(ignite != null); + Debug.Assert(cacheConfiguration != null); + Debug.Assert(cacheValueType != null); + + _parser = queryParser; + _executor = executor; + _ignite = ignite; + _cacheConfiguration = cacheConfiguration; + + if (tableName != null) + { + _tableName = tableName; + + ValidateTableName(); + } + else + _tableName = InferTableName(cacheValueType); + } + + /// <summary> + /// Gets the ignite. + /// </summary> + public IIgnite Ignite + { + get { return _ignite; } + } + + /// <summary> + /// Gets the name of the cache. + /// </summary> + public CacheConfiguration CacheConfiguration + { + get { return _cacheConfiguration; } + } + + /// <summary> + /// Gets the name of the table. + /// </summary> + public string TableName + { + get { return _tableName; } + } + + /// <summary> + /// Gets the executor. + /// </summary> + public CacheFieldsQueryExecutor Executor + { + get { return _executor; } + } + + /// <summary> + /// Generates the query model. + /// </summary> + public QueryModel GenerateQueryModel(Expression expression) + { + return _parser.GetParsedQuery(expression); + } + + /** <inheritdoc /> */ + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")] + public IQueryable CreateQuery(Expression expression) + { + Debug.Assert(expression != null); + + var elementType = GetItemTypeOfClosedGenericIEnumerable(expression.Type, "expression"); + + // Slow, but this method is never called during normal LINQ usage with generics + return (IQueryable) GenericCreateQueryMethod.MakeGenericMethod(elementType) + .Invoke(this, new object[] {expression}); + } + + /** <inheritdoc /> */ + public IQueryable<T> CreateQuery<T>(Expression expression) + { + return new CacheFieldsQueryable<T>(this, expression); + } + + /** <inheritdoc /> */ + object IQueryProvider.Execute(Expression expression) + { + return Execute(expression); + } + + /** <inheritdoc /> */ + public TResult Execute<TResult>(Expression expression) + { + return (TResult) Execute(expression).Value; + } + + /// <summary> + /// Executes the specified expression. + /// </summary> + private IStreamedData Execute(Expression expression) + { + var model = GenerateQueryModel(expression); + + return model.Execute(_executor); + } + + /// <summary> + /// Validates the name of the table. + /// </summary> + private void ValidateTableName() + { + var validTableNames = GetValidTableNames(); + + if (!validTableNames.Contains(_tableName, StringComparer.OrdinalIgnoreCase)) + { + throw new CacheException(string.Format("Invalid table name specified for CacheQueryable: '{0}'; " + + "configured table names are: {1}", + _tableName, + validTableNames.Aggregate((x, y) => x + ", " + y))); + } + } + + /// <summary> + /// Gets the valid table names for current cache. + /// </summary> + private string[] GetValidTableNames() + { + // Split on '.' to throw away Java type namespace + var validTableNames = _cacheConfiguration.QueryEntities == null + ? null + : _cacheConfiguration.QueryEntities.Select(e => e.ValueTypeName.Split('.').Last()).ToArray(); + + if (validTableNames == null || !validTableNames.Any()) + throw new CacheException(string.Format("Queries are not configured for cache '{0}'", + _cacheConfiguration.Name ?? "null")); + + return validTableNames; + } + + /// <summary> + /// Infers the name of the table from cache configuration. + /// </summary> + /// <param name="cacheValueType"></param> + private string InferTableName(Type cacheValueType) + { + var validTableNames = GetValidTableNames(); + + if (validTableNames.Length == 1) + return validTableNames[0]; + + var valueTypeName = cacheValueType.Name; + + 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", + validTableNames.Aggregate((x, y) => x + ", " + y))); + } + + /// <summary> + /// Gets the item type of closed generic i enumerable. + /// </summary> + private static Type GetItemTypeOfClosedGenericIEnumerable(Type enumerableType, string argumentName) + { + Type itemType; + + if (!ItemTypeReflectionUtility.TryGetItemTypeOfClosedGenericIEnumerable(enumerableType, out itemType)) + { + var message = string.Format("Expected a closed generic type implementing IEnumerable<T>, " + + "but found '{0}'.", enumerableType); + + throw new ArgumentException(message, argumentName); + } + + return itemType; + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryable.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryable.cs b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryable.cs new file mode 100644 index 0000000..2db5399 --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheFieldsQueryable.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.Linq.Impl +{ + using System.Linq; + using System.Linq.Expressions; + using Apache.Ignite.Core.Cache; + + /// <summary> + /// Fields <see cref="IQueryable{T}"/> implementation for <see cref="ICache{TK,TV}"/>. + /// </summary> + internal class CacheFieldsQueryable<T> : CacheQueryableBase<T> + { + /// <summary> + /// Initializes a new instance of the <see cref="CacheQueryable{TKey, TValue}"/> class. + /// </summary> + /// <param name="provider">The provider used to execute the query represented by this queryable + /// and to construct new queries.</param> + /// <param name="expression">The expression representing the query.</param> + public CacheFieldsQueryable(IQueryProvider provider, Expression expression) : base(provider, expression) + { + // No-op. + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/ef642e91/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 new file mode 100644 index 0000000..eaca07a --- /dev/null +++ b/modules/platforms/dotnet/Apache.Ignite.Linq/Impl/CacheQueryExpressionVisitor.cs @@ -0,0 +1,506 @@ +/* + * 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. + */ + +using System; +using System.Text; + +namespace Apache.Ignite.Linq.Impl +{ + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using Apache.Ignite.Core.Cache; + using Apache.Ignite.Core.Cache.Configuration; + using Apache.Ignite.Core.Impl.Common; + using Remotion.Linq.Clauses; + using Remotion.Linq.Clauses.Expressions; + using Remotion.Linq.Clauses.ResultOperators; + using Remotion.Linq.Parsing; + + /// <summary> + /// Expression visitor, transforms query subexpressions (such as Where clauses) to SQL. + /// </summary> + internal class CacheQueryExpressionVisitor : ThrowingExpressionVisitor + { + /** */ + private readonly bool _useStar; + + /** */ + private readonly CacheQueryModelVisitor _modelVisitor; + + /** */ + private static readonly CopyOnWriteConcurrentDictionary<MemberInfo, string> FieldNameMap = + new CopyOnWriteConcurrentDictionary<MemberInfo, string>(); + + /// <summary> + /// Initializes a new instance of the <see cref="CacheQueryExpressionVisitor" /> class. + /// </summary> + /// <param name="modelVisitor">The _model visitor.</param> + /// <param name="useStar">Flag indicating that star '*' qualifier should be used + /// for the whole-table select instead of _key, _val.</param> + public CacheQueryExpressionVisitor(CacheQueryModelVisitor modelVisitor, bool useStar) + { + Debug.Assert(modelVisitor != null); + + _modelVisitor = modelVisitor; + _useStar = useStar; + } + + /// <summary> + /// Gets the result builder. + /// </summary> + public StringBuilder ResultBuilder + { + get { return _modelVisitor.Builder; } + } + + /// <summary> + /// Gets the parameters. + /// </summary> + public IList<object> Parameters + { + get { return _modelVisitor.Parameters; } + } + + /// <summary> + /// Gets the aliases. + /// </summary> + private AliasDictionary Aliases + { + get { return _modelVisitor.Aliases; } + } + + /** <inheritdoc /> */ + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")] + protected override Expression VisitUnary(UnaryExpression expression) + { + ResultBuilder.Append("("); + + switch (expression.NodeType) + { + case ExpressionType.Negate: + ResultBuilder.Append("-"); + break; + case ExpressionType.Not: + ResultBuilder.Append("not "); + break; + case ExpressionType.Convert: + // Ignore, let the db do the conversion + break; + default: + return base.VisitUnary(expression); + } + + Visit(expression.Operand); + + ResultBuilder.Append(")"); + + return expression; + } + + /// <summary> + /// Visits the binary function. + /// </summary> + /// <param name="expression">The expression.</param> + /// <returns>True if function detected, otherwise false.</returns> + private bool VisitBinaryFunc(BinaryExpression expression) + { + if (expression.NodeType == ExpressionType.Add && expression.Left.Type == typeof (string)) + ResultBuilder.Append("concat("); + else if (expression.NodeType == ExpressionType.Coalesce) + ResultBuilder.Append("coalesce("); + else + return false; + + Visit(expression.Left); + ResultBuilder.Append(", "); + Visit(expression.Right); + ResultBuilder.Append(")"); + + return true; + } + + /** <inheritdoc /> */ + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")] + protected override Expression VisitBinary(BinaryExpression expression) + { + // Either func or operator + if (VisitBinaryFunc(expression)) + return expression; + + ResultBuilder.Append("("); + + Visit(expression.Left); + + switch (expression.NodeType) + { + case ExpressionType.Equal: + { + var rightConst = expression.Right as ConstantExpression; + + if (rightConst != null && rightConst.Value == null) + { + // Special case for nulls, since "= null" does not work in SQL + ResultBuilder.Append(" is null)"); + return expression; + } + + ResultBuilder.Append(" = "); + break; + } + + case ExpressionType.NotEqual: + { + var rightConst = expression.Right as ConstantExpression; + + if (rightConst != null && rightConst.Value == null) + { + // Special case for nulls, since "<> null" does not work in SQL + ResultBuilder.Append(" is not null)"); + return expression; + } + + ResultBuilder.Append(" <> "); + break; + } + + case ExpressionType.AndAlso: + case ExpressionType.And: + ResultBuilder.Append(" and "); + break; + + case ExpressionType.OrElse: + case ExpressionType.Or: + ResultBuilder.Append(" or "); + break; + + case ExpressionType.Add: + ResultBuilder.Append(" + "); + break; + + case ExpressionType.Subtract: + ResultBuilder.Append(" - "); + break; + + case ExpressionType.Multiply: + ResultBuilder.Append(" * "); + break; + + case ExpressionType.Modulo: + ResultBuilder.Append(" % "); + break; + + case ExpressionType.Divide: + ResultBuilder.Append(" / "); + break; + + case ExpressionType.GreaterThan: + ResultBuilder.Append(" > "); + break; + + case ExpressionType.GreaterThanOrEqual: + ResultBuilder.Append(" >= "); + break; + + case ExpressionType.LessThan: + ResultBuilder.Append(" < "); + break; + + case ExpressionType.LessThanOrEqual: + ResultBuilder.Append(" <= "); + break; + + case ExpressionType.Coalesce: + break; + + default: + base.VisitBinary(expression); + break; + } + + Visit(expression.Right); + ResultBuilder.Append(")"); + + return expression; + } + + /** <inheritdoc /> */ + protected override Expression VisitQuerySourceReference(QuerySourceReferenceExpression expression) + { + // Count, sum, max, min expect a single field or * + // In other cases we need both parts of cache entry + var format = _useStar ? "{0}.*" : "{0}._key, {0}._val"; + + var tableName = Aliases.GetTableAlias(ExpressionWalker.GetCacheQueryable(expression)); + + ResultBuilder.AppendFormat(format, tableName); + + return expression; + } + + /** <inheritdoc /> */ + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")] + protected override Expression VisitMember(MemberExpression expression) + { + // Field hierarchy is flattened (Person.Address.Street is just Street), append as is, do not call Visit. + + // Special case: string.Length + if (expression.Member == MethodVisitor.StringLength) + { + ResultBuilder.Append("length("); + + VisitMember((MemberExpression) expression.Expression); + + ResultBuilder.Append(")"); + + return expression; + } + + // Special case: grouping + if (VisitGroupByMember(expression.Expression)) + return expression; + + var queryable = ExpressionWalker.GetCacheQueryable(expression, false); + + if (queryable != null) + { + var fieldName = GetFieldName(expression, queryable); + + ResultBuilder.AppendFormat("{0}.{1}", Aliases.GetTableAlias(queryable), fieldName); + } + else + AppendParameter(RegisterEvaluatedParameter(expression)); + + return expression; + } + + /// <summary> + /// Registers query parameter that is evaluated from a lambda expression argument. + /// </summary> + public object RegisterEvaluatedParameter(Expression expression) + { + _modelVisitor.ParameterExpressions.Add(expression); + + return ExpressionWalker.EvaluateExpression<object>(expression); + } + + /// <summary> + /// Gets the name of the field from a member expression. + /// </summary> + private static string GetFieldName(MemberExpression expression, ICacheQueryableInternal queryable) + { + var fieldName = GetMemberFieldName(expression.Member); + + // Look for a field alias + var cacheCfg = queryable.CacheConfiguration; + + if (cacheCfg.QueryEntities == null || cacheCfg.QueryEntities.All(x => x.Aliases == null)) + return fieldName; // There are no aliases defined - early exit + + // 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].Name) && + (e.ValueType == keyValTypes[1] || e.ValueTypeName == keyValTypes[1].Name)); + + if (entity == null) + return fieldName; + + // There are some aliases for the current query type + // Calculate full field name and look for alias + var fullFieldName = fieldName; + var member = expression; + + while ((member = member.Expression as MemberExpression) != null && + member.Member.DeclaringType != queryable.ElementType) + fullFieldName = GetFieldName(member, queryable) + "." + fullFieldName; + + var alias = entity.Aliases.Where(x => x.FullName == fullFieldName) + .Select(x => x.Alias).FirstOrDefault(); + + return alias ?? fieldName; + } + + /// <summary> + /// Gets the name of the member field. + /// </summary> + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", + Justification = "Not applicable.")] + private static string GetMemberFieldName(MemberInfo member) + { + string fieldName; + + if (FieldNameMap.TryGetValue(member, out fieldName)) + return fieldName; + + return FieldNameMap.GetOrAdd(member, m => + { + // Special case: _key, _val + if (m.DeclaringType != null && + m.DeclaringType.IsGenericType && + m.DeclaringType.GetGenericTypeDefinition() == typeof (ICacheEntry<,>)) + return "_" + m.Name.ToLowerInvariant().Substring(0, 3); + + var qryFieldAttr = m.GetCustomAttributes(true) + .OfType<QuerySqlFieldAttribute>().FirstOrDefault(); + + return qryFieldAttr == null || string.IsNullOrEmpty(qryFieldAttr.Name) + ? m.Name + : qryFieldAttr.Name; + }); + } + + /// <summary> + /// Visits the group by member. + /// </summary> + private bool VisitGroupByMember(Expression expression) + { + var srcRef = expression as QuerySourceReferenceExpression; + if (srcRef == null) + return false; + + var from = srcRef.ReferencedQuerySource as IFromClause; + if (from == null) + return false; + + var subQry = from.FromExpression as SubQueryExpression; + if (subQry == null) + return false; + + var grpBy = subQry.QueryModel.ResultOperators.OfType<GroupResultOperator>().FirstOrDefault(); + if (grpBy == null) + return false; + + Visit(grpBy.KeySelector); + + return true; + } + + /** <inheritdoc /> */ + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")] + protected override Expression VisitConstant(ConstantExpression expression) + { + AppendParameter(expression.Value); + + return expression; + } + + /// <summary> + /// Appends the parameter. + /// </summary> + private void AppendParameter(object value) + { + ResultBuilder.Append("?"); + + _modelVisitor.Parameters.Add(value); + } + + /** <inheritdoc /> */ + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")] + protected override Expression VisitMethodCall(MethodCallExpression expression) + { + MethodVisitor.VisitMethodCall(expression, this); + + return expression; + } + + /** <inheritdoc /> */ + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")] + protected override Expression VisitNew(NewExpression expression) + { + VisitArguments(expression.Arguments); + + return expression; + } + + /** <inheritdoc /> */ + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")] + protected override Expression VisitInvocation(InvocationExpression expression) + { + VisitArguments(expression.Arguments); + + return expression; + } + + /** <inheritdoc /> */ + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")] + protected override Expression VisitConditional(ConditionalExpression expression) + { + ResultBuilder.Append("casewhen("); + + Visit(expression.Test); + + // Explicit type specification is required when all arguments of CASEWHEN are parameters + ResultBuilder.Append(", cast("); + Visit(expression.IfTrue); + ResultBuilder.AppendFormat(" as {0}), ", SqlTypes.GetSqlTypeName(expression.Type) ?? "other"); + + Visit(expression.IfFalse); + ResultBuilder.Append(")"); + + return expression; + } + + /** <inheritdoc /> */ + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods")] + protected override Expression VisitSubQuery(SubQueryExpression expression) + { + // This happens when New expression uses a subquery, in a GroupBy. + _modelVisitor.VisitSelectors(expression.QueryModel, false); + + return expression; + } + + /** <inheritdoc /> */ + protected override Exception CreateUnhandledItemException<T>(T unhandledItem, string visitMethod) + { + return new NotSupportedException(string.Format("The expression '{0}' (type: {1}) is not supported.", + unhandledItem, typeof (T))); + } + + /// <summary> + /// Visits multiple arguments. + /// </summary> + /// <param name="arguments">The arguments.</param> + private void VisitArguments(IEnumerable<Expression> arguments) + { + var first = true; + + foreach (var e in arguments) + { + if (!first) + { + if (_useStar) + throw new NotSupportedException("Aggregate functions do not support multiple fields"); + + ResultBuilder.Append(", "); + } + + first = false; + + Visit(e); + } + } + } +}
