Skip to content

Commit

Permalink
Adds Min/Max support using OrderBy + Take(1).
Browse files Browse the repository at this point in the history
  • Loading branch information
matteobortolazzo committed May 5, 2019
1 parent 8ba8738 commit 243c5e0
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 9 deletions.
53 changes: 44 additions & 9 deletions src/CouchDB.Driver/CouchQueryProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public CouchQueryProvider(IFlurlClient flurlClient, CouchSettings settings, stri

public override string GetQueryText(Expression expression)
{
return Translate(expression);
return Translate(ref expression);
}

public override object Execute(Expression expression, bool completeResponse)
Expand All @@ -38,7 +38,7 @@ public override object Execute(Expression expression, bool completeResponse)
var unsupportedMethodCallExpressions = new List<MethodCallExpression>();
expression = RemoveUnsupportedMethodExpressions(expression, out var hasUnsupportedMethods, unsupportedMethodCallExpressions);

var body = Translate(expression);
var body = Translate(ref expression);
Type elementType = TypeSystem.GetElementType(expression.Type);

// Create generic GetCouchList method and invoke it, sending the request to CouchDB
Expand All @@ -60,12 +60,15 @@ public override object Execute(Expression expression, bool completeResponse)
return result;
}

private string Translate(Expression e)
private string Translate(ref Expression e)
{
e = Evaluator.PartialEval(e);
var whereVisitor = new WhereExpressionVisitor();
e = whereVisitor.Visit(e);

var pretranslator = new QueryPretranslator();
e = pretranslator.Visit(e);

return new QueryTranslator(_settings).Translate(e);
}

Expand Down Expand Up @@ -104,6 +107,12 @@ bool IsUnsupportedMethodCallExpression(Expression ex)
unsupportedMethodCallExpressions.Add(m);
return isUnsupported;
}
// If the next call is supported and the current is in the composite list
if (QueryTranslator.CompositeQueryableMethods.Contains(m.Method.Name))
{
unsupportedMethodCallExpressions.Add(m);
return true;
}
// If the next call is supported and the current is not in the supported list
if (!QueryTranslator.NativeQueryableMethods.Contains(m.Method.Name))
{
Expand All @@ -123,13 +132,25 @@ private object InvokeUnsupportedMethodCallExpression(object result, MethodCallEx
{
MethodInfo queryableMethodInfo = methodCallExpression.Method;
Expression[] queryableMethodArguments = methodCallExpression.Arguments.ToArray();
// Find the equivalent method in Enumerable
MethodInfo enumarableMethodInfo = typeof(Enumerable).GetMethods().Single(enumerableMethodInfo =>

// Since Max and Min are not map 1 to 1 from Queryable to Enumerable
// they need to be handled differently
MethodInfo FindEnumerableMethod()
{
return
queryableMethodInfo.Name == enumerableMethodInfo.Name &&
ReflectionComparator.IsCompatible(queryableMethodInfo, enumerableMethodInfo);
});
if (queryableMethodInfo.Name == nameof(Queryable.Max) || queryableMethodInfo.Name == nameof(Queryable.Min))
{
return FindEnumerableMinMax(queryableMethodInfo);
}
return typeof(Enumerable).GetMethods().Single(enumerableMethodInfo =>
{
return
queryableMethodInfo.Name == enumerableMethodInfo.Name &&
ReflectionComparator.IsCompatible(queryableMethodInfo, enumerableMethodInfo);
});
}

// Find the equivalent method in Enumerable
MethodInfo enumarableMethodInfo = FindEnumerableMethod();

// Add the list as first parameter of the call
var invokeParameter = new List<object> { result };
Expand Down Expand Up @@ -158,5 +179,19 @@ private object GetArgumentValueFromExpression(Expression e)
}
throw new NotImplementedException($"Expression of type {e.NodeType} not supported.");
}

private static MethodInfo FindEnumerableMinMax(MethodInfo queryableMethodInfo)
{
Type[] genericParams = queryableMethodInfo.GetGenericArguments();
MethodInfo finalMethodInfo = typeof(Enumerable).GetMethods().Single(enumerableMethodInfo =>
{
Type[] enumerableArguments = enumerableMethodInfo.GetGenericArguments();
return
enumerableMethodInfo.Name == queryableMethodInfo.Name &&
enumerableArguments.Length == genericParams.Length - 1 &&
enumerableMethodInfo.ReturnType == genericParams[1];
});
return finalMethodInfo;
}
}
}
31 changes: 31 additions & 0 deletions src/CouchDB.Driver/ExpressionVisitors/QueryPretranslator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace CouchDB.Driver.ExpressionVisitors
{
public class QueryPretranslator : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.Name == nameof(Queryable.Min))
{
Type[] genericArgs = node.Method.GetGenericArguments();
MethodCallExpression orderByDescExpression = Expression.Call(typeof(Queryable), nameof(Queryable.OrderBy), genericArgs, node.Arguments[0], node.Arguments[1]);
MethodCallExpression takeExpression = Expression.Call(typeof(Queryable), nameof(Queryable.Take), genericArgs.Take(1).ToArray(), orderByDescExpression, Expression.Constant(1));
return takeExpression;
}
if (node.Method.Name == nameof(Queryable.Max))
{
Type[] genericArgs = node.Method.GetGenericArguments();
MethodCallExpression orderByDescExpression = Expression.Call(typeof(Queryable), nameof(Queryable.OrderByDescending), genericArgs, node.Arguments[0], node.Arguments[1]);
MethodCallExpression takeExpression = Expression.Call(typeof(Queryable), nameof(Queryable.Take), genericArgs.Take(1).ToArray(), orderByDescExpression, Expression.Constant(1));
return takeExpression;
}
return base.VisitMethodCall(node);
}
}
}
10 changes: 10 additions & 0 deletions src/CouchDB.Driver/Translators/MethodCallExpressionTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ internal partial class QueryTranslator
"Select"
};

internal static List<string> CompositeQueryableMethods { get; } = new List<string>
{
"Max",
"Min",
//"First",
//"FirstOrDefault",
//"Last",
//"LastOrDefault"
};

private static Expression StripQuotes(Expression e)
{
while (e.NodeType == ExpressionType.Quote)
Expand Down
62 changes: 62 additions & 0 deletions tests/CouchDB.Driver.UnitTests/SupportByCombination_Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CouchDB.Driver.UnitTests.Models;
using Flurl.Http.Testing;
using Xunit;

namespace CouchDB.Driver.UnitTests
{
public class SupportByCombination_Tests
{
private readonly CouchDatabase<Rebel> _rebels;
private readonly Rebel _mainRebel;
private readonly List<Rebel> _rebelsList;
private object _response;

public SupportByCombination_Tests()
{
var client = new CouchClient("http://localhost");
_rebels = client.GetDatabase<Rebel>();
_mainRebel = new Rebel
{
Id = Guid.NewGuid().ToString(),
Name = "Luke",
Age = 19,
Skills = new List<string> { "Force" }
};
_rebelsList = new List<Rebel>
{
_mainRebel
};
_response = new
{
Docs = _rebelsList
};
}

[Fact]
public async Task Max()
{
using (var httpTest = new HttpTest())
{
httpTest.RespondWithJson(_response);
var result = _rebels.AsQueryable().Max(r => r.Age);
Assert.Equal(_mainRebel.Age, result);
}
}

[Fact]
public async Task Min()
{
using (var httpTest = new HttpTest())
{
httpTest.RespondWithJson(_response);
var result = _rebels.AsQueryable().Min(r => r.Age);
Assert.Equal(_mainRebel.Age, result);
}
}
}
}

0 comments on commit 243c5e0

Please sign in to comment.