Skip to content

Commit c325624

Browse files
author
Matteo Bortolazzo
authored
Merge pull request #42 from matteobortolazzo/dev
Release 1.1.1
2 parents 6e9dab1 + d62aa61 commit c325624

16 files changed

+226
-69
lines changed

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
# 1.1.0 (2019-05-05)
1+
# 1.1.1 (2019-06-02)
2+
3+
## Features
4+
* **Single/SingleOrDefault:** Methods implementated as composite supported methods (Where and Take(2)).
5+
6+
## Bug Fixes
7+
* **Queries:** Implicit bools in nested methods. ([#PR41](https://github.com/matteobortolazzo/couchdb-net/pull/41))
8+
* **FxCopAnalyzers:** Removed from NuGet dependencies.
9+
10+
# 1.1.0 (2019-05-05)
211

312
## Features
413
* **_find:** IQueryable methods that are not supported by CouchDB are evaluated in-memory using the IEnumerable counterpart, if possible.

LATEST_CHANGE.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
## Features
2-
* **_find:** IQueryable methods that are not supported by CouchDB are evaluated in-memory using the IEnumerable counterpart, if possible.
2+
* **Single/SingleOrDefault:** Methods implementated as composite supported methods (Where and Take(2)).
3+
4+
## Bug Fixes
5+
* **Queries:** Implicit bools in nested methods. ([#PR41](https://github.com/matteobortolazzo/couchdb-net/pull/41))
6+
* **FxCopAnalyzers:** Removed from NuGet dependencies.

README.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -181,16 +181,21 @@ If the Where method is not called in the expression, it will at an empty selecto
181181

182182
### Composite methods
183183

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

186187
| Input | Output |
187188
|:----------------------------------|:--------------------------------------|
188189
| Min(r => r.Age) | OrderBy(r => r.Age).Take(1) |
189190
| Max(r => r.Age) | OrderByDescending(r => r.Age).Take(1) |
190-
| Single() | Take(1) |
191-
| SingleOrDefault() | Take(1) |
192-
| Single(r => r.Age == 19) | Where(r => r.Age == 19).Take(1) |
193-
| SingleOrDefault(r => r.Age == 19) | Where(r => r.Age == 19).Take(1) |
191+
| First() | Take(1) |
192+
| FirstOrDefault() | Take(1) |
193+
| First(r => r.Age == 19) | Where(r => r.Age == 19).Take(1) |
194+
| FirstOrDefault(r => r.Age == 19) | Where(r => r.Age == 19).Take(1) |
195+
| Single() | Take(2) |
196+
| SingleOrDefault() | Take(2) |
197+
| Single(r => r.Age == 19) | Where(r => r.Age == 19).Take(2) |
198+
| SingleOrDefault(r => r.Age == 19) | Where(r => r.Age == 19).Take(2) |
194199

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

src/CouchDB.Driver/CouchDB.Driver.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
88
<Description>A .NET Standard driver for CouchDB.</Description>
99
<PackageProjectUrl>https://github.com/matteobortolazzo/couchdb-net</PackageProjectUrl>
10-
<RepositoryUrl>https://github.com/matteobortolazzo/couchdb-ne</RepositoryUrl>
10+
<RepositoryUrl>https://github.com/matteobortolazzo/couchdb-net</RepositoryUrl>
1111
<PackageTags>couchdb,driver,nosql,netstandard,pouchdb,xamarin</PackageTags>
1212
<PackageReleaseNotes></PackageReleaseNotes>
1313
<PackageIconUrl>http://couchdb.apache.org/image/[email protected]</PackageIconUrl>
@@ -26,7 +26,7 @@
2626
<ItemGroup>
2727
<PackageReference Include="Flurl.Http" Version="2.4.1" />
2828
<PackageReference Include="Humanizer.Core" Version="2.6.2" />
29-
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.1" />
29+
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" PrivateAssets="All" Version="2.9.1" />
3030
<PackageReference Include="Nito.AsyncEx.Context" Version="1.1.0" />
3131
</ItemGroup>
3232

src/CouchDB.Driver/CouchQueryProvider.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,19 @@ MethodInfo FindEnumerableMethod()
163163
Type[] genericParameters = queryableMethodInfo.GetGenericArguments();
164164
Type[] usableParameters = genericParameters.Take(requestedGenericParameters.Length).ToArray();
165165
MethodInfo enumarableGenericMethod = enumarableMethodInfo.MakeGenericMethod(usableParameters);
166-
var filtered = enumarableGenericMethod.Invoke(null, invokeParameter.ToArray());
167-
return filtered;
166+
try
167+
{
168+
var filtered = enumarableGenericMethod.Invoke(null, invokeParameter.ToArray());
169+
return filtered;
170+
}
171+
catch (TargetInvocationException e)
172+
{
173+
if (e.InnerException != null)
174+
{
175+
throw e.InnerException;
176+
}
177+
throw;
178+
}
168179
}
169180

170181
private object GetArgumentValueFromExpression(Expression e)

src/CouchDB.Driver/ExpressionVisitors/BoolMemberToConstantEvaluator.cs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,31 @@
44

55
namespace CouchDB.Driver.CompositeExpressionsEvaluator
66
{
7-
internal class BoolMemberToConstantEvaluator : ExpressionVisitor
7+
internal class BoolMemberToConstantEvaluator : ExpressionVisitor
88
{
9-
private bool _visitingWhereMethod;
9+
private bool _isVisitingWhereMethodOrChild;
1010

1111
protected override Expression VisitMethodCall(MethodCallExpression m)
1212
{
13-
_visitingWhereMethod = m.Method.Name == nameof(Queryable.Where) && m.Method.DeclaringType == typeof(Queryable);
14-
if (_visitingWhereMethod)
13+
bool isRootWhereMethod = !_isVisitingWhereMethodOrChild && m.Method.Name == nameof(Queryable.Where) && m.Method.DeclaringType == typeof(Queryable);
14+
if (isRootWhereMethod)
1515
{
16-
Expression result = base.VisitMethodCall(m);
17-
_visitingWhereMethod = false;
18-
return result;
16+
_isVisitingWhereMethodOrChild = true;
1917
}
20-
return base.VisitMethodCall(m);
18+
19+
Expression result = base.VisitMethodCall(m);
20+
21+
if (isRootWhereMethod)
22+
{
23+
_isVisitingWhereMethodOrChild = false;
24+
}
25+
26+
return result;
2127
}
2228

2329
protected override Expression VisitBinary(BinaryExpression expression)
2430
{
25-
if (_visitingWhereMethod && expression.Right is ConstantExpression c && c.Type == typeof(bool) &&
31+
if (_isVisitingWhereMethodOrChild && expression.Right is ConstantExpression c && c.Type == typeof(bool) &&
2632
(expression.NodeType == ExpressionType.Equal || expression.NodeType == ExpressionType.NotEqual))
2733
{
2834
return expression;
@@ -50,8 +56,8 @@ protected override Expression VisitUnary(UnaryExpression expression)
5056

5157
private bool IsWhereBooleanExpression(MemberExpression expression)
5258
{
53-
return _visitingWhereMethod &&
54-
expression.Member is PropertyInfo info &&
59+
return _isVisitingWhereMethodOrChild &&
60+
expression.Member is PropertyInfo info &&
5561
info.PropertyType == typeof(bool);
5662
}
5763
}

src/CouchDB.Driver/ExpressionVisitors/CompositeExpressionsEvaluator.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
1212
var numberOfParameters = node.Method.GetParameters().Length;
1313

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

2020
// Min(e => e.P) == OrderBy(e => e.P).Take(1) + Min
@@ -29,7 +29,7 @@ MethodCallExpression GetTakeOneExpression(Expression previousExpression)
2929
MethodCallExpression orderBy = Expression.Call(typeof(Queryable), nameof(Queryable.OrderByDescending), genericArgs, node.Arguments[0], node.Arguments[1]);
3030
return GetTakeOneExpression(orderBy);
3131
}
32-
// Single and SingleOrDefault have the same behaviour
32+
// First and FirstOrDefault have the same behaviour
3333
if (node.Method.Name == nameof(Queryable.First) || node.Method.Name == nameof(Queryable.FirstOrDefault))
3434
{
3535
// First() == Take(1) + First
@@ -44,6 +44,21 @@ MethodCallExpression GetTakeOneExpression(Expression previousExpression)
4444
return GetTakeOneExpression(whereExpression);
4545
}
4646
}
47+
// Single and SingleOrDefault have the same behaviour
48+
if (node.Method.Name == nameof(Queryable.Single) || node.Method.Name == nameof(Queryable.SingleOrDefault))
49+
{
50+
// Single() == Take(2) + Single
51+
if (numberOfParameters == 1)
52+
{
53+
return GetTakeOneExpression(node.Arguments[0], 2);
54+
}
55+
// SingleOrDefault(e => e.P) == Where(e => e.P).Take(2) + Single
56+
else if (numberOfParameters == 2)
57+
{
58+
MethodCallExpression whereExpression = Expression.Call(typeof(Queryable), nameof(Queryable.Where), genericArgs, node.Arguments[0], node.Arguments[1]);
59+
return GetTakeOneExpression(whereExpression, 2);
60+
}
61+
}
4762
return base.VisitMethodCall(node);
4863
}
4964
}

tests/CouchDB.Driver.E2ETests/Client_Tests.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
namespace CouchDB.Driver.E2E
1010
{
11+
[Trait("Category", "Integration")]
1112
public class ClientTests
1213
{
1314
[Fact]
@@ -54,10 +55,10 @@ public async Task Users()
5455
{
5556
users = await client.CreateDatabaseAsync<CouchUser>().ConfigureAwait(false);
5657
}
57-
58+
5859
var luke = await users.CreateAsync(new CouchUser(name: "luke", password: "lasersword")).ConfigureAwait(false);
5960
Assert.Equal("luke", luke.Name);
60-
61+
6162
luke = await users.FindAsync(luke.Id).ConfigureAwait(false);
6263
Assert.Equal("luke", luke.Name);
6364

tests/CouchDB.Driver.UnitTests/Authentication_Test.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public async Task None()
1616
{
1717
using (var httpTest = new HttpTest())
1818
{
19-
// ToList
19+
// ToListAssert
2020
httpTest.RespondWithJson(new { Docs = new List<string>() });
2121
// Logout
2222
httpTest.RespondWithJson(new { ok = true });

tests/CouchDB.Driver.UnitTests/Find/Find_Selector.cs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,24 @@ namespace CouchDB.Driver.UnitTests.Find
88
{
99
public class Find_Selector
1010
{
11-
private readonly CouchDatabase<Rebel> rebels;
11+
private readonly CouchDatabase<Rebel> _rebels;
1212

1313
public Find_Selector()
1414
{
1515
var client = new CouchClient("http://localhost");
16-
rebels = client.GetDatabase<Rebel>();
16+
_rebels = client.GetDatabase<Rebel>();
1717
}
1818

1919
[Fact]
2020
public void ToList_EmptySelector()
2121
{
22-
var json = rebels.ToString();
22+
var json = _rebels.ToString();
2323
Assert.Equal(@"{""selector"":{}}", json);
2424
}
2525
[Fact]
2626
public void ComplexQuery()
2727
{
28-
var json = rebels.Where(r =>
28+
var json = _rebels.Where(r =>
2929
r.Age == 19 &&
3030
(r.Name == "Luke" || r.Name == "Leia") &&
3131
r.Skills.Contains("force")).ToString();
@@ -35,79 +35,79 @@ public void ComplexQuery()
3535
public void Variable_Const()
3636
{
3737
var age = 19;
38-
var json = rebels.Where(r => r.Age == age).ToString();
38+
var json = _rebels.Where(r => r.Age == age).ToString();
3939
Assert.Equal(@"{""selector"":{""age"":19}}", json);
4040
}
4141
[Fact]
4242
public void Variable_Object()
4343
{
4444
var luke = new Rebel { Age = 19 };
45-
var json = rebels.Where(r => r.Age == luke.Age).ToString();
45+
var json = _rebels.Where(r => r.Age == luke.Age).ToString();
4646
Assert.Equal(@"{""selector"":{""age"":19}}", json);
4747
}
4848
[Fact]
4949
public void ExpressionVariable_Const()
5050
{
5151
Expression<Func<Rebel, bool>> filter = r => r.Age == 19;
52-
var json = rebels.Where(filter).ToString();
52+
var json = _rebels.Where(filter).ToString();
5353
Assert.Equal(@"{""selector"":{""age"":19}}", json);
5454
}
5555
[Fact]
5656
public void ExpressionVariable_Object()
5757
{
5858
var luke = new Rebel { Age = 19 };
5959
Expression<Func<Rebel, bool>> filter = r => r.Age == luke.Age;
60-
var json = rebels.Where(filter).ToString();
60+
var json = _rebels.Where(filter).ToString();
6161
Assert.Equal(@"{""selector"":{""age"":19}}", json);
6262
}
6363
[Fact]
6464
public void Enum()
6565
{
66-
var json = rebels.Where(r => r.Species == Species.Human).ToString();
66+
var json = _rebels.Where(r => r.Species == Species.Human).ToString();
6767
Assert.Equal(@"{""selector"":{""species"":0}}", json);
6868
}
6969
[Fact]
7070
public void GuidQuery()
7171
{
7272
var guidString = "83c79283-f634-41e3-8aab-674bdbae3413";
7373
var guid = Guid.Parse(guidString);
74-
var json = rebels.Where(r => r.Guid == guid).ToString();
74+
var json = _rebels.Where(r => r.Guid == guid).ToString();
7575
Assert.Equal(@"{""selector"":{""guid"":""83c79283-f634-41e3-8aab-674bdbae3413""}}", json);
7676
}
7777
[Fact]
7878
public void Variable_Bool_True()
7979
{
80-
var json = rebels.Where(r => r.IsJedi).OrderBy(r => r.IsJedi).ToString();
80+
var json = _rebels.Where(r => r.IsJedi).OrderBy(r => r.IsJedi).ToString();
8181
Assert.Equal(@"{""selector"":{""isJedi"":true},""sort"":[""isJedi""]}", json);
8282
}
8383
[Fact]
8484
public void Variable_Bool_False()
8585
{
86-
var json = rebels.Where(r => !r.IsJedi).OrderBy(r => r.IsJedi).ToString();
86+
var json = _rebels.Where(r => !r.IsJedi).OrderBy(r => r.IsJedi).ToString();
8787
Assert.Equal(@"{""selector"":{""isJedi"":false},""sort"":[""isJedi""]}", json);
8888
}
8989
[Fact]
9090
public void Variable_Bool_ExplicitTrue()
9191
{
92-
var json = rebels.Where(r => r.IsJedi == true).OrderBy(r => r.IsJedi).ToString();
92+
var json = _rebels.Where(r => r.IsJedi == true).OrderBy(r => r.IsJedi).ToString();
9393
Assert.Equal(@"{""selector"":{""isJedi"":true},""sort"":[""isJedi""]}", json);
9494
}
9595
[Fact]
9696
public void Variable_Bool_ExplicitFalse()
9797
{
98-
var json = rebels.Where(r => r.IsJedi == false).OrderBy(r => r.IsJedi).ToString();
98+
var json = _rebels.Where(r => r.IsJedi == false).OrderBy(r => r.IsJedi).ToString();
9999
Assert.Equal(@"{""selector"":{""isJedi"":false},""sort"":[""isJedi""]}", json);
100100
}
101101
[Fact]
102102
public void Variable_Bool_ExplicitNotTrue()
103103
{
104-
var json = rebels.Where(r => r.IsJedi != true).OrderBy(r => r.IsJedi).ToString();
104+
var json = _rebels.Where(r => r.IsJedi != true).OrderBy(r => r.IsJedi).ToString();
105105
Assert.Equal(@"{""selector"":{""isJedi"":{""$ne"":true}},""sort"":[""isJedi""]}", json);
106106
}
107107
[Fact]
108108
public void Variable_Bool_ExplicitNotFalse()
109109
{
110-
var json = rebels.Where(r => r.IsJedi != false).OrderBy(r => r.IsJedi).ToString();
110+
var json = _rebels.Where(r => r.IsJedi != false).OrderBy(r => r.IsJedi).ToString();
111111
Assert.Equal(@"{""selector"":{""isJedi"":{""$ne"":false}},""sort"":[""isJedi""]}", json);
112112
}
113113
}

tests/CouchDB.Driver.UnitTests/Find/Find_Selector_Combinations.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,40 @@ public void ElemMatch()
5959
Assert.Equal(@"{""selector"":{""battles"":{""$elemMatch"":{""planet"":""Naboo""}}}}", json);
6060
}
6161
[Fact]
62+
public void ElemMatchImplicitBool()
63+
{
64+
var json = _rebels.Where(r => r.Battles.Any(b => b.DidWin)).ToString();
65+
Assert.Equal(@"{""selector"":{""battles"":{""$elemMatch"":{""didWin"":true}}}}", json);
66+
}
67+
[Fact]
68+
public void ElemMatchBoolExplicit()
69+
{
70+
var json = _rebels.Where(r => r.Battles.Any(b => b.DidWin == true)).ToString();
71+
Assert.Equal(@"{""selector"":{""battles"":{""$elemMatch"":{""didWin"":true}}}}", json);
72+
}
73+
[Fact]
74+
public void ElemMatchNested()
75+
{
76+
var json = _rebels.Where(r => r.Battles.Any(b => b.Vehicles.Any(v => v.CanFly == true))).ToString();
77+
Assert.Equal(@"{""selector"":{""battles"":{""$elemMatch"":{""vehicles"":{""$elemMatch"":{""canFly"":true}}}}}}", json);
78+
}
79+
[Fact]
80+
public void ElemMatchNestedImplicitBool()
81+
{
82+
var json = _rebels.Where(r => r.Battles.Any(b => b.Vehicles.Any(v => v.CanFly))).ToString();
83+
Assert.Equal(@"{""selector"":{""battles"":{""$elemMatch"":{""vehicles"":{""$elemMatch"":{""canFly"":true}}}}}}", json);
84+
}
85+
[Fact]
6286
public void AllMatch()
6387
{
6488
var json = _rebels.Where(r => r.Battles.All(b => b.Planet == "Naboo")).ToString();
6589
Assert.Equal(@"{""selector"":{""battles"":{""$allMatch"":{""planet"":""Naboo""}}}}", json);
6690
}
91+
[Fact]
92+
public void AllMatchImplicitBool()
93+
{
94+
var json = _rebels.Where(r => r.Battles.All(b => b.DidWin)).ToString();
95+
Assert.Equal(@"{""selector"":{""battles"":{""$allMatch"":{""didWin"":true}}}}", json);
96+
}
6797
}
6898
}

0 commit comments

Comments
 (0)