Skip to content

Commit 3462a99

Browse files
Merge pull request #168 from matteobortolazzo/dev
v3.2.0
2 parents 79e69fe + 6838665 commit 3462a99

20 files changed

+418
-33
lines changed

CHANGELOG.md

+16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
# 3.2.0 (2022-07-03)
2+
3+
## Features
4+
5+
* **Replication**: Adds support to replication ([#151](https://github.com/matteobortolazzo/couchdb-net/pull/151))
6+
* **Attachments**: Adds DownloadAttachmentAsStreamAsync ([#152](https://github.com/matteobortolazzo/couchdb-net/pull/152))
7+
* **IsMatch**: Support multiline regex ([#161](https://github.com/matteobortolazzo/couchdb-net/pull/161))
8+
9+
## Bug Fixes
10+
11+
* **ElementAt**: Fixes query on .NET 6. ([#156](https://github.com/matteobortolazzo/couchdb-net/pull/156))
12+
* **Attachments**: Fixes attachments in FindAsync. ([#159](https://github.com/matteobortolazzo/couchdb-net/pull/159))
13+
* **Attachments**: Fixes attachments uploads ([#159](https://github.com/matteobortolazzo/couchdb-net/pull/159))
14+
* **Attachments**: Fixes Bad Request on attachment upload. ([#164](https://github.com/matteobortolazzo/couchdb-net/pull/164))
15+
* **GetInfoAsync**: Fixed 32-bit integer overflow. ([#165](https://github.com/matteobortolazzo/couchdb-net/pull/165))
16+
117
# 3.1.1 (2021-10-14)
218

319
## Bug Fixes

LATEST_CHANGE.md

+12-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
## Bug Fixes
1+
## Features
22

3-
* **Query**: Fix First/Last with conditions fail. ([#142](https://github.com/matteobortolazzo/couchdb-net/issues/142))
4-
* **Query**: Fix First/Last on splitted database. ([#136](https://github.com/matteobortolazzo/couchdb-net/issues/136))
5-
* **Query**: Throws exception on List.Count instead of wrong query. ([#138](https://github.com/matteobortolazzo/couchdb-net/issues/138))
6-
* **Query**: Fix multi thread call issues. ([#133](https://github.com/matteobortolazzo/couchdb-net/issues/133))
7-
* **FindManyAsync**: Filters out null results. ([#141](https://github.com/matteobortolazzo/couchdb-net/issues/141)) Thanks [AlexandrSHad](https://github.com/AlexandrSHad)
8-
* **Continuous Changes**: Fix multi thread issues. ([#140](https://github.com/matteobortolazzo/couchdb-net/issues/140))
3+
* **Replication**: Adds support to replication ([#151](https://github.com/matteobortolazzo/couchdb-net/pull/151))
4+
* **Attachments**: Adds DownloadAttachmentAsStreamAsync ([#152](https://github.com/matteobortolazzo/couchdb-net/pull/152))
5+
* **IsMatch**: Support multiline regex ([#161](https://github.com/matteobortolazzo/couchdb-net/pull/161))
6+
7+
## Bug Fixes
8+
9+
* **ElementAt**: Fixes query on .NET 6. ([#156](https://github.com/matteobortolazzo/couchdb-net/pull/156))
10+
* **Attachments**: Fixes attachments in FindAsync. ([#159](https://github.com/matteobortolazzo/couchdb-net/pull/159))
11+
* **Attachments**: Fixes attachments uploads ([#159](https://github.com/matteobortolazzo/couchdb-net/pull/159))
12+
* **Attachments**: Fixes Bad Request on attachment upload. ([#164](https://github.com/matteobortolazzo/couchdb-net/pull/164))
13+
* **GetInfoAsync**: Fixed 32-bit integer overflow. ([#165](https://github.com/matteobortolazzo/couchdb-net/pull/165))

README.md

+26-1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ The produced Mango JSON:
109109
* [Local (non-replicating) Documents](#local-(non-replicating)-documents)
110110
* [Bookmark and Execution stats](#bookmark-and-execution-stats)
111111
* [Users](#users)
112+
* [Replication](#replication)
112113
* [Dependency Injection](#dependency-injection)
113114
* [Advanced](#advanced)
114115
* [Contributors](#contributors)
@@ -384,6 +385,8 @@ foreach (CouchAttachment attachment in luke.Attachments)
384385

385386
// Download
386387
string downloadFilePath = await rebels.DownloadAttachment(attachment, downloadFolderPath, "luke-downloaded.txt");
388+
//or
389+
Stream responseStream = await rebels.DownloadAttachmentAsStreamAsync(attachment);
387390
```
388391

389392
## DB Changes Feed
@@ -601,7 +604,7 @@ var docs = await local.GetAsync(searchOpt);
601604

602605
### Bookmark and Execution stats
603606

604-
If bookmark and execution stats must be retrived, call *ToCouchList* or *ToCouchListAsync*.
607+
If bookmark and execution stats must be retrieved, call *ToCouchList* or *ToCouchListAsync*.
605608

606609
```csharp
607610
var allRebels = await rebels.ToCouchListAsync();
@@ -634,6 +637,28 @@ To change password:
634637
luke = await users.ChangeUserPassword(luke, "r2d2");
635638
```
636639

640+
### Replication
641+
642+
The driver provides the ability to configure and cancel replication between databases.
643+
644+
```csharp
645+
if (await client.ReplicateAsync("anakin", "jedi", new CouchReplication() { Continuous = true}))
646+
{
647+
await client.RemoveReplicationAsync("anakin", "jedi", new CouchReplication() { Continuous = true });
648+
}
649+
```
650+
651+
It is also possible to specify a selector to apply to the replication
652+
```csharp
653+
await client.ReplicateAsync("stormtroopers", "deathstar", new CouchReplication() { Continuous = true, Selector = new { designation = "FN-2187" } }));
654+
```
655+
656+
Credentials can be specified as follows
657+
```csharp
658+
await client.ReplicateAsync("luke", "jedi", new CouchReplication() { SourceCredentials = new CouchReplicationBasicCredentials()username: "luke", password: "r2d2") }));
659+
```
660+
661+
637662
## Dependency Injection
638663

639664
As always you can leverage all the benefits of Dependency Injection.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using CouchDB.Driver.Types;
5+
using Newtonsoft.Json;
6+
7+
namespace CouchDB.Driver.Converters
8+
{
9+
internal class AttachmentsParsedConverter : JsonConverter<Dictionary<string, CouchAttachment>>
10+
{
11+
public override void WriteJson(JsonWriter writer, Dictionary<string, CouchAttachment> value,
12+
JsonSerializer serializer)
13+
{
14+
serializer.Serialize(writer, value
15+
.Where(kvp => kvp.Value.FileInfo is null)
16+
.ToDictionary(k => k.Key, v => v.Value)
17+
);
18+
}
19+
20+
public override bool CanRead => false;
21+
22+
public override Dictionary<string, CouchAttachment> ReadJson(JsonReader reader, Type objectType,
23+
Dictionary<string, CouchAttachment> existingValue, bool hasExistingValue,
24+
JsonSerializer serializer)
25+
{
26+
throw new NotImplementedException();
27+
}
28+
}
29+
}

src/CouchDB.Driver/CouchClient.cs

+106
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,112 @@ public async Task<IEnumerable<CouchActiveTask>> GetActiveTasksAsync(Cancellation
314314

315315
#endregion
316316

317+
#region Replication
318+
public async Task<bool> ReplicateAsync(string source, string target, CouchReplication? replication = null, bool persistent = true, CancellationToken cancellationToken = default)
319+
{
320+
var request = NewRequest();
321+
322+
if (replication == null)
323+
{
324+
replication = new CouchReplication();
325+
}
326+
327+
if (replication.SourceCredentials == null)
328+
{
329+
replication.Source = source;
330+
}
331+
else
332+
{
333+
replication.Source = new CouchReplicationHost()
334+
{
335+
Url = source,
336+
Auth = new CouchReplicationAuth()
337+
{
338+
BasicCredentials = replication.SourceCredentials,
339+
}
340+
};
341+
}
342+
343+
if (replication.TargetCredentials == null)
344+
{
345+
replication.Target = target;
346+
}
347+
else
348+
{
349+
replication.Target = new CouchReplicationHost()
350+
{
351+
Url = target,
352+
Auth = new CouchReplicationAuth()
353+
{
354+
BasicCredentials = replication.TargetCredentials,
355+
}
356+
};
357+
}
358+
359+
OperationResult result = await request
360+
.AppendPathSegments(persistent ? "_replicator" : "_replicate")
361+
.PostJsonAsync(replication, cancellationToken)
362+
.SendRequestAsync()
363+
.ReceiveJson<OperationResult>()
364+
.ConfigureAwait(false);
365+
366+
return result.Ok;
367+
}
368+
369+
public async Task<bool> RemoveReplicationAsync(string source, string target, CouchReplication? replication = null, bool persistent = true, CancellationToken cancellationToken = default)
370+
{
371+
var request = NewRequest();
372+
373+
if (replication == null)
374+
{
375+
replication = new CouchReplication();
376+
}
377+
378+
if (replication.SourceCredentials == null)
379+
{
380+
replication.Source = source;
381+
}
382+
else
383+
{
384+
replication.Source = new CouchReplicationHost()
385+
{
386+
Url = source,
387+
Auth = new CouchReplicationAuth()
388+
{
389+
BasicCredentials = replication.SourceCredentials,
390+
}
391+
};
392+
}
393+
394+
if (replication.TargetCredentials == null)
395+
{
396+
replication.Target = target;
397+
}
398+
else
399+
{
400+
replication.Target = new CouchReplicationHost()
401+
{
402+
Url = target,
403+
Auth = new CouchReplicationAuth()
404+
{
405+
BasicCredentials = replication.TargetCredentials,
406+
}
407+
};
408+
}
409+
410+
replication.Cancel = true;
411+
412+
OperationResult result = await request
413+
.AppendPathSegments(persistent ? "_replicator" : "_replicate")
414+
.PostJsonAsync(replication, cancellationToken)
415+
.SendRequestAsync()
416+
.ReceiveJson<OperationResult>()
417+
.ConfigureAwait(false);
418+
419+
return result.Ok;
420+
}
421+
#endregion
422+
317423
#endregion
318424

319425
#region Implementations

src/CouchDB.Driver/CouchDB.Driver.csproj

+2-3
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,8 @@
3232
</PropertyGroup>
3333

3434
<ItemGroup>
35-
<PackageReference Include="Flurl.Http" Version="3.0.1" />
36-
<PackageReference Include="Humanizer.Core" Version="2.8.26" />
37-
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" PrivateAssets="All" Version="3.3.1" />
35+
<PackageReference Include="Flurl.Http" Version="3.2.0" />
36+
<PackageReference Include="Humanizer.Core" Version="2.11.10" />
3837
</ItemGroup>
3938

4039
<ItemGroup>

src/CouchDB.Driver/CouchDatabase.cs

+31-3
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,19 @@ internal CouchDatabase(IFlurlClient flurlClient, CouchOptions options, QueryCont
8989
.GetAsync(cancellationToken)
9090
.ConfigureAwait(false);
9191

92-
return response != null && response.StatusCode == (int)HttpStatusCode.OK
93-
? await response.GetJsonAsync<TSource>().ConfigureAwait(false)
94-
: null;
92+
TSource? document = null;
93+
if (response is not { StatusCode: (int)HttpStatusCode.OK })
94+
{
95+
return document;
96+
}
97+
98+
document = await response.GetJsonAsync<TSource>().ConfigureAwait(false);
99+
if (document != null)
100+
{
101+
InitAttachments(document);
102+
}
103+
104+
return document;
95105
}
96106

97107
/// <inheritdoc />
@@ -602,6 +612,24 @@ public async Task<string> DownloadAttachmentAsync(CouchAttachment attachment, st
602612
.ConfigureAwait(false);
603613
}
604614

615+
/// <inheritdoc />
616+
public async Task<Stream> DownloadAttachmentAsStreamAsync(CouchAttachment attachment, CancellationToken cancellationToken = default)
617+
{
618+
Check.NotNull(attachment, nameof(attachment));
619+
620+
if (attachment.Uri == null)
621+
{
622+
throw new InvalidOperationException("The attachment is not uploaded yet.");
623+
}
624+
625+
return await NewRequest()
626+
.AppendPathSegment(attachment.DocumentId)
627+
.AppendPathSegment(Uri.EscapeUriString(attachment.Name))
628+
.WithHeader("If-Match", attachment.DocumentRev)
629+
.GetStreamAsync(cancellationToken)
630+
.ConfigureAwait(false);
631+
}
632+
605633
/// <inheritdoc />
606634
public async Task CompactAsync(CancellationToken cancellationToken = default)
607635
{

src/CouchDB.Driver/ICouchDatabase.cs

+9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.IO;
34
using System.Linq;
45
using System.Threading;
56
using System.Threading.Tasks;
@@ -228,6 +229,14 @@ Task<string> CreateIndexAsync(string name, Action<IIndexBuilder<TSource>> indexB
228229
Task<string> DownloadAttachmentAsync(CouchAttachment attachment, string localFolderPath,
229230
string? localFileName = null, int bufferSize = 4096, CancellationToken cancellationToken = default);
230231

232+
/// <summary>
233+
/// Asynchronously downloads a specific attachment as stream.
234+
/// </summary>
235+
/// <param name="attachment">The attachment to download.</param>
236+
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
237+
/// <returns>A task that represents the asynchronous operation. The task result contains of the file stream.</returns>
238+
Task<Stream> DownloadAttachmentAsStreamAsync(CouchAttachment attachment, CancellationToken cancellationToken = default);
239+
231240
/// <summary>
232241
/// Requests compaction of the specified database.
233242
/// </summary>

src/CouchDB.Driver/Query/Translators/MethodCallExpressionTranslator.cs

+12-4
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ protected override Expression VisitMethodCall(MethodCallExpression m)
170170

171171
throw new NotSupportedException($"The method '{m.Method.Name}' is not supported");
172172
}
173-
173+
174174
#region Queryable
175175

176176
private Expression VisitWhereMethod(MethodCallExpression m)
@@ -219,7 +219,7 @@ private Expression VisitOrderDescendingMethod(Expression m)
219219
{
220220
void InspectOrdering(Expression e)
221221
{
222-
MethodCallExpression o = e as MethodCallExpression?? throw new AuthenticationException($"Invalid expression type {e.GetType().Name}");
222+
MethodCallExpression o = e as MethodCallExpression ?? throw new AuthenticationException($"Invalid expression type {e.GetType().Name}");
223223
Expression lambdaBody = o.GetLambdaBody();
224224

225225
switch (o.Method.Name)
@@ -494,10 +494,18 @@ private Expression VisitIsCouchTypeMethod(MethodCallExpression m)
494494
private Expression VisitIsMatchMethod(MethodCallExpression m)
495495
{
496496
_sb.Append('{');
497+
497498
Visit(m.Arguments[0]);
498-
_sb.Append(":{\"$regex\":");
499+
var isParameter = m.Arguments[0].NodeType == ExpressionType.Parameter;
500+
if (!isParameter)
501+
{
502+
_sb.Append(":{");
503+
}
504+
505+
_sb.Append("\"$regex\":");
499506
Visit(m.Arguments[1]);
500-
_sb.Append("}}");
507+
_sb.Append(!isParameter ? "}}" : "}");
508+
501509
return m;
502510
}
503511

src/CouchDB.Driver/Shared/QueryableMethods.cs

+10-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Reflection;
@@ -195,9 +195,13 @@ static QueryableMethods()
195195
mi => mi.Name == nameof(Queryable.Max) && mi.GetParameters().Length == 1);
196196

197197
ElementAt = queryableMethods.Single(
198-
mi => mi.Name == nameof(Queryable.ElementAt) && mi.GetParameters().Length == 2);
198+
mi => mi.Name == nameof(Queryable.ElementAt)
199+
&& mi.GetParameters().Length == 2
200+
&& mi.GetParameters()[1].ParameterType == typeof(int));
199201
ElementAtOrDefault = queryableMethods.Single(
200-
mi => mi.Name == nameof(Queryable.ElementAtOrDefault) && mi.GetParameters().Length == 2);
202+
mi => mi.Name == nameof(Queryable.ElementAtOrDefault)
203+
&& mi.GetParameters().Length == 2
204+
&& mi.GetParameters()[1].ParameterType == typeof(int));
201205
FirstWithoutPredicate = queryableMethods.Single(
202206
mi => mi.Name == nameof(Queryable.First) && mi.GetParameters().Length == 1);
203207
FirstWithPredicate = queryableMethods.Single(
@@ -250,7 +254,9 @@ static QueryableMethods()
250254
Skip = queryableMethods.Single(
251255
mi => mi.Name == nameof(Queryable.Skip) && mi.GetParameters().Length == 2);
252256
Take = queryableMethods.Single(
253-
mi => mi.Name == nameof(Queryable.Take) && mi.GetParameters().Length == 2);
257+
mi => mi.Name == nameof(Queryable.Take)
258+
&& mi.GetParameters().Length == 2
259+
&& mi.GetParameters()[1].ParameterType == typeof(int));
254260
SkipWhile = queryableMethods.Single(
255261
mi => mi.Name == nameof(Queryable.SkipWhile)
256262
&& mi.GetParameters().Length == 2

0 commit comments

Comments
 (0)