Skip to content

Release 1.1.1 #42

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 2, 2019
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
# 1.1.0 (2019-05-05)
# 1.1.1 (2019-06-02)

## Features
* **Single/SingleOrDefault:** Methods implementated as composite supported methods (Where and Take(2)).

## Bug Fixes
* **Queries:** Implicit bools in nested methods. ([#PR41](https://github.com/matteobortolazzo/couchdb-net/pull/41))
* **FxCopAnalyzers:** Removed from NuGet dependencies.

# 1.1.0 (2019-05-05)

## Features
* **_find:** IQueryable methods that are not supported by CouchDB are evaluated in-memory using the IEnumerable counterpart, if possible.
Expand Down
6 changes: 5 additions & 1 deletion LATEST_CHANGE.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
## Features
* **_find:** IQueryable methods that are not supported by CouchDB are evaluated in-memory using the IEnumerable counterpart, if possible.
* **Single/SingleOrDefault:** Methods implementated as composite supported methods (Where and Take(2)).

## Bug Fixes
* **Queries:** Implicit bools in nested methods. ([#PR41](https://github.com/matteobortolazzo/couchdb-net/pull/41))
* **FxCopAnalyzers:** Removed from NuGet dependencies.
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,16 +181,21 @@ If the Where method is not called in the expression, it will at an empty selecto

### Composite methods

Some methods that are not directly supported by CouchDB are converted to a composition of supported ones.
Some methods that are not directly supported by CouchDB are converted to a composition of supported ones,
and the in-memory LINQ method will be executed at the end.

| Input | Output |
|:----------------------------------|:--------------------------------------|
| Min(r => r.Age) | OrderBy(r => r.Age).Take(1) |
| Max(r => r.Age) | OrderByDescending(r => r.Age).Take(1) |
| Single() | Take(1) |
| SingleOrDefault() | Take(1) |
| Single(r => r.Age == 19) | Where(r => r.Age == 19).Take(1) |
| SingleOrDefault(r => r.Age == 19) | Where(r => r.Age == 19).Take(1) |
| First() | Take(1) |
| FirstOrDefault() | Take(1) |
| First(r => r.Age == 19) | Where(r => r.Age == 19).Take(1) |
| FirstOrDefault(r => r.Age == 19) | Where(r => r.Age == 19).Take(1) |
| Single() | Take(2) |
| SingleOrDefault() | Take(2) |
| Single(r => r.Age == 19) | Where(r => r.Age == 19).Take(2) |
| SingleOrDefault(r => r.Age == 19) | Where(r => r.Age == 19).Take(2) |

**WARN**: Do not call a method twice, for example: `Where(func).Single(func)` won't work.

Expand Down
4 changes: 2 additions & 2 deletions src/CouchDB.Driver/CouchDB.Driver.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<Description>A .NET Standard driver for CouchDB.</Description>
<PackageProjectUrl>https://github.com/matteobortolazzo/couchdb-net</PackageProjectUrl>
<RepositoryUrl>https://github.com/matteobortolazzo/couchdb-ne</RepositoryUrl>
<RepositoryUrl>https://github.com/matteobortolazzo/couchdb-net</RepositoryUrl>
<PackageTags>couchdb,driver,nosql,netstandard,pouchdb,xamarin</PackageTags>
<PackageReleaseNotes></PackageReleaseNotes>
<PackageIconUrl>http://couchdb.apache.org/image/[email protected]</PackageIconUrl>
Expand All @@ -26,7 +26,7 @@
<ItemGroup>
<PackageReference Include="Flurl.Http" Version="2.4.1" />
<PackageReference Include="Humanizer.Core" Version="2.6.2" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.1" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" PrivateAssets="All" Version="2.9.1" />
<PackageReference Include="Nito.AsyncEx.Context" Version="1.1.0" />
</ItemGroup>

Expand Down
15 changes: 13 additions & 2 deletions src/CouchDB.Driver/CouchQueryProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,19 @@ MethodInfo FindEnumerableMethod()
Type[] genericParameters = queryableMethodInfo.GetGenericArguments();
Type[] usableParameters = genericParameters.Take(requestedGenericParameters.Length).ToArray();
MethodInfo enumarableGenericMethod = enumarableMethodInfo.MakeGenericMethod(usableParameters);
var filtered = enumarableGenericMethod.Invoke(null, invokeParameter.ToArray());
return filtered;
try
{
var filtered = enumarableGenericMethod.Invoke(null, invokeParameter.ToArray());
return filtered;
}
catch (TargetInvocationException e)
{
if (e.InnerException != null)
{
throw e.InnerException;
}
throw;
}
}

private object GetArgumentValueFromExpression(Expression e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,31 @@

namespace CouchDB.Driver.CompositeExpressionsEvaluator
{
internal class BoolMemberToConstantEvaluator : ExpressionVisitor
internal class BoolMemberToConstantEvaluator : ExpressionVisitor
{
private bool _visitingWhereMethod;
private bool _isVisitingWhereMethodOrChild;

protected override Expression VisitMethodCall(MethodCallExpression m)
{
_visitingWhereMethod = m.Method.Name == nameof(Queryable.Where) && m.Method.DeclaringType == typeof(Queryable);
if (_visitingWhereMethod)
bool isRootWhereMethod = !_isVisitingWhereMethodOrChild && m.Method.Name == nameof(Queryable.Where) && m.Method.DeclaringType == typeof(Queryable);
if (isRootWhereMethod)
{
Expression result = base.VisitMethodCall(m);
_visitingWhereMethod = false;
return result;
_isVisitingWhereMethodOrChild = true;
}
return base.VisitMethodCall(m);

Expression result = base.VisitMethodCall(m);

if (isRootWhereMethod)
{
_isVisitingWhereMethodOrChild = false;
}

return result;
}

protected override Expression VisitBinary(BinaryExpression expression)
{
if (_visitingWhereMethod && expression.Right is ConstantExpression c && c.Type == typeof(bool) &&
if (_isVisitingWhereMethodOrChild && expression.Right is ConstantExpression c && c.Type == typeof(bool) &&
(expression.NodeType == ExpressionType.Equal || expression.NodeType == ExpressionType.NotEqual))
{
return expression;
Expand Down Expand Up @@ -50,8 +56,8 @@ protected override Expression VisitUnary(UnaryExpression expression)

private bool IsWhereBooleanExpression(MemberExpression expression)
{
return _visitingWhereMethod &&
expression.Member is PropertyInfo info &&
return _isVisitingWhereMethodOrChild &&
expression.Member is PropertyInfo info &&
info.PropertyType == typeof(bool);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
var numberOfParameters = node.Method.GetParameters().Length;

// Return an expression representing Queryable<T>.Take(1)
MethodCallExpression GetTakeOneExpression(Expression previousExpression)
MethodCallExpression GetTakeOneExpression(Expression previousExpression, int numberOfElements = 1)
{
return Expression.Call(typeof(Queryable), nameof(Queryable.Take), genericArgs.Take(1).ToArray(), previousExpression, Expression.Constant(1));
return Expression.Call(typeof(Queryable), nameof(Queryable.Take), genericArgs.Take(1).ToArray(), previousExpression, Expression.Constant(numberOfElements));
}

// Min(e => e.P) == OrderBy(e => e.P).Take(1) + Min
Expand All @@ -29,7 +29,7 @@ MethodCallExpression GetTakeOneExpression(Expression previousExpression)
MethodCallExpression orderBy = Expression.Call(typeof(Queryable), nameof(Queryable.OrderByDescending), genericArgs, node.Arguments[0], node.Arguments[1]);
return GetTakeOneExpression(orderBy);
}
// Single and SingleOrDefault have the same behaviour
// First and FirstOrDefault have the same behaviour
if (node.Method.Name == nameof(Queryable.First) || node.Method.Name == nameof(Queryable.FirstOrDefault))
{
// First() == Take(1) + First
Expand All @@ -44,6 +44,21 @@ MethodCallExpression GetTakeOneExpression(Expression previousExpression)
return GetTakeOneExpression(whereExpression);
}
}
// Single and SingleOrDefault have the same behaviour
if (node.Method.Name == nameof(Queryable.Single) || node.Method.Name == nameof(Queryable.SingleOrDefault))
{
// Single() == Take(2) + Single
if (numberOfParameters == 1)
{
return GetTakeOneExpression(node.Arguments[0], 2);
}
// SingleOrDefault(e => e.P) == Where(e => e.P).Take(2) + Single
else if (numberOfParameters == 2)
{
MethodCallExpression whereExpression = Expression.Call(typeof(Queryable), nameof(Queryable.Where), genericArgs, node.Arguments[0], node.Arguments[1]);
return GetTakeOneExpression(whereExpression, 2);
}
}
return base.VisitMethodCall(node);
}
}
Expand Down
5 changes: 3 additions & 2 deletions tests/CouchDB.Driver.E2ETests/Client_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

namespace CouchDB.Driver.E2E
{
[Trait("Category", "Integration")]
public class ClientTests
{
[Fact]
Expand Down Expand Up @@ -54,10 +55,10 @@ public async Task Users()
{
users = await client.CreateDatabaseAsync<CouchUser>().ConfigureAwait(false);
}

var luke = await users.CreateAsync(new CouchUser(name: "luke", password: "lasersword")).ConfigureAwait(false);
Assert.Equal("luke", luke.Name);

luke = await users.FindAsync(luke.Id).ConfigureAwait(false);
Assert.Equal("luke", luke.Name);

Expand Down
2 changes: 1 addition & 1 deletion tests/CouchDB.Driver.UnitTests/Authentication_Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public async Task None()
{
using (var httpTest = new HttpTest())
{
// ToList
// ToListAssert
httpTest.RespondWithJson(new { Docs = new List<string>() });
// Logout
httpTest.RespondWithJson(new { ok = true });
Expand Down
32 changes: 16 additions & 16 deletions tests/CouchDB.Driver.UnitTests/Find/Find_Selector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,24 @@ namespace CouchDB.Driver.UnitTests.Find
{
public class Find_Selector
{
private readonly CouchDatabase<Rebel> rebels;
private readonly CouchDatabase<Rebel> _rebels;

public Find_Selector()
{
var client = new CouchClient("http://localhost");
rebels = client.GetDatabase<Rebel>();
_rebels = client.GetDatabase<Rebel>();
}

[Fact]
public void ToList_EmptySelector()
{
var json = rebels.ToString();
var json = _rebels.ToString();
Assert.Equal(@"{""selector"":{}}", json);
}
[Fact]
public void ComplexQuery()
{
var json = rebels.Where(r =>
var json = _rebels.Where(r =>
r.Age == 19 &&
(r.Name == "Luke" || r.Name == "Leia") &&
r.Skills.Contains("force")).ToString();
Expand All @@ -35,79 +35,79 @@ public void ComplexQuery()
public void Variable_Const()
{
var age = 19;
var json = rebels.Where(r => r.Age == age).ToString();
var json = _rebels.Where(r => r.Age == age).ToString();
Assert.Equal(@"{""selector"":{""age"":19}}", json);
}
[Fact]
public void Variable_Object()
{
var luke = new Rebel { Age = 19 };
var json = rebels.Where(r => r.Age == luke.Age).ToString();
var json = _rebels.Where(r => r.Age == luke.Age).ToString();
Assert.Equal(@"{""selector"":{""age"":19}}", json);
}
[Fact]
public void ExpressionVariable_Const()
{
Expression<Func<Rebel, bool>> filter = r => r.Age == 19;
var json = rebels.Where(filter).ToString();
var json = _rebels.Where(filter).ToString();
Assert.Equal(@"{""selector"":{""age"":19}}", json);
}
[Fact]
public void ExpressionVariable_Object()
{
var luke = new Rebel { Age = 19 };
Expression<Func<Rebel, bool>> filter = r => r.Age == luke.Age;
var json = rebels.Where(filter).ToString();
var json = _rebels.Where(filter).ToString();
Assert.Equal(@"{""selector"":{""age"":19}}", json);
}
[Fact]
public void Enum()
{
var json = rebels.Where(r => r.Species == Species.Human).ToString();
var json = _rebels.Where(r => r.Species == Species.Human).ToString();
Assert.Equal(@"{""selector"":{""species"":0}}", json);
}
[Fact]
public void GuidQuery()
{
var guidString = "83c79283-f634-41e3-8aab-674bdbae3413";
var guid = Guid.Parse(guidString);
var json = rebels.Where(r => r.Guid == guid).ToString();
var json = _rebels.Where(r => r.Guid == guid).ToString();
Assert.Equal(@"{""selector"":{""guid"":""83c79283-f634-41e3-8aab-674bdbae3413""}}", json);
}
[Fact]
public void Variable_Bool_True()
{
var json = rebels.Where(r => r.IsJedi).OrderBy(r => r.IsJedi).ToString();
var json = _rebels.Where(r => r.IsJedi).OrderBy(r => r.IsJedi).ToString();
Assert.Equal(@"{""selector"":{""isJedi"":true},""sort"":[""isJedi""]}", json);
}
[Fact]
public void Variable_Bool_False()
{
var json = rebels.Where(r => !r.IsJedi).OrderBy(r => r.IsJedi).ToString();
var json = _rebels.Where(r => !r.IsJedi).OrderBy(r => r.IsJedi).ToString();
Assert.Equal(@"{""selector"":{""isJedi"":false},""sort"":[""isJedi""]}", json);
}
[Fact]
public void Variable_Bool_ExplicitTrue()
{
var json = rebels.Where(r => r.IsJedi == true).OrderBy(r => r.IsJedi).ToString();
var json = _rebels.Where(r => r.IsJedi == true).OrderBy(r => r.IsJedi).ToString();
Assert.Equal(@"{""selector"":{""isJedi"":true},""sort"":[""isJedi""]}", json);
}
[Fact]
public void Variable_Bool_ExplicitFalse()
{
var json = rebels.Where(r => r.IsJedi == false).OrderBy(r => r.IsJedi).ToString();
var json = _rebels.Where(r => r.IsJedi == false).OrderBy(r => r.IsJedi).ToString();
Assert.Equal(@"{""selector"":{""isJedi"":false},""sort"":[""isJedi""]}", json);
}
[Fact]
public void Variable_Bool_ExplicitNotTrue()
{
var json = rebels.Where(r => r.IsJedi != true).OrderBy(r => r.IsJedi).ToString();
var json = _rebels.Where(r => r.IsJedi != true).OrderBy(r => r.IsJedi).ToString();
Assert.Equal(@"{""selector"":{""isJedi"":{""$ne"":true}},""sort"":[""isJedi""]}", json);
}
[Fact]
public void Variable_Bool_ExplicitNotFalse()
{
var json = rebels.Where(r => r.IsJedi != false).OrderBy(r => r.IsJedi).ToString();
var json = _rebels.Where(r => r.IsJedi != false).OrderBy(r => r.IsJedi).ToString();
Assert.Equal(@"{""selector"":{""isJedi"":{""$ne"":false}},""sort"":[""isJedi""]}", json);
}
}
Expand Down
30 changes: 30 additions & 0 deletions tests/CouchDB.Driver.UnitTests/Find/Find_Selector_Combinations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,40 @@ public void ElemMatch()
Assert.Equal(@"{""selector"":{""battles"":{""$elemMatch"":{""planet"":""Naboo""}}}}", json);
}
[Fact]
public void ElemMatchImplicitBool()
{
var json = _rebels.Where(r => r.Battles.Any(b => b.DidWin)).ToString();
Assert.Equal(@"{""selector"":{""battles"":{""$elemMatch"":{""didWin"":true}}}}", json);
}
[Fact]
public void ElemMatchBoolExplicit()
{
var json = _rebels.Where(r => r.Battles.Any(b => b.DidWin == true)).ToString();
Assert.Equal(@"{""selector"":{""battles"":{""$elemMatch"":{""didWin"":true}}}}", json);
}
[Fact]
public void ElemMatchNested()
{
var json = _rebels.Where(r => r.Battles.Any(b => b.Vehicles.Any(v => v.CanFly == true))).ToString();
Assert.Equal(@"{""selector"":{""battles"":{""$elemMatch"":{""vehicles"":{""$elemMatch"":{""canFly"":true}}}}}}", json);
}
[Fact]
public void ElemMatchNestedImplicitBool()
{
var json = _rebels.Where(r => r.Battles.Any(b => b.Vehicles.Any(v => v.CanFly))).ToString();
Assert.Equal(@"{""selector"":{""battles"":{""$elemMatch"":{""vehicles"":{""$elemMatch"":{""canFly"":true}}}}}}", json);
}
[Fact]
public void AllMatch()
{
var json = _rebels.Where(r => r.Battles.All(b => b.Planet == "Naboo")).ToString();
Assert.Equal(@"{""selector"":{""battles"":{""$allMatch"":{""planet"":""Naboo""}}}}", json);
}
[Fact]
public void AllMatchImplicitBool()
{
var json = _rebels.Where(r => r.Battles.All(b => b.DidWin)).ToString();
Assert.Equal(@"{""selector"":{""battles"":{""$allMatch"":{""didWin"":true}}}}", json);
}
}
}
Loading