Skip to content

Commit 5d403f7

Browse files
Merge pull request matteobortolazzo#170 from dmlarionov/master
Support for revisions in Find, AddOrUpdate
2 parents 40cf372 + 9224b31 commit 5d403f7

File tree

8 files changed

+218
-15
lines changed

8 files changed

+218
-15
lines changed

README.md

+12-1
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,17 @@ string downloadFilePath = await rebels.DownloadAttachment(attachment, downloadFo
389389
Stream responseStream = await rebels.DownloadAttachmentAsStreamAsync(attachment);
390390
```
391391

392+
## Revisions
393+
394+
The options for `FindAsync(..)` and `AddOrUpdateAsync(..)` support passing revision:
395+
396+
```csharp
397+
await _rebels.FindAsync("1", new FindOptions { Rev = "1-xxx" });
398+
await _rebels.AddOrUpdateAsync(r, new AddOrUpdateOptions { Rev = "1-xxx" });
399+
```
400+
401+
For attachements revisions are supported by `CouchAttachment` class which is passing `DocumentRev` to `DownloadAttachmentAsync(..)` and `DownloadAttachmentAsStreamAsync(..)`.
402+
392403
## DB Changes Feed
393404

394405
The following *feed modes* are supported: `normal`, `longpool` and `continuous`.
@@ -741,4 +752,4 @@ Also, the configurator has `ConfigureFlurlClient` to set custom HTTP client opti
741752

742753
[Benjamin Höglinger-Stelzer](https://github.com/nefarius), [mwasson74](https://github.com/mwasson74), [Emre ÇAĞLAR](https://github.com/emrecaglar): Attachments improvments and fixes.
743754

744-
[Dhiren Sham](https://github.com/dhirensham): Implementing replication.
755+
[Dhiren Sham](https://github.com/dhirensham): Implementing replication.

src/CouchDB.Driver/CouchDatabase.cs

+26-14
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
using System.Net;
2727
using System.Text.RegularExpressions;
2828
using CouchDB.Driver.Views;
29+
using CouchDB.Driver.DatabaseApiMethodOptions;
2930

3031
namespace CouchDB.Driver
3132
{
@@ -73,16 +74,20 @@ internal CouchDatabase(IFlurlClient flurlClient, CouchOptions options, QueryCont
7374
#region Find
7475

7576
/// <inheritdoc />
76-
public async Task<TSource?> FindAsync(string docId, bool withConflicts = false,
77-
CancellationToken cancellationToken = default)
77+
public Task<TSource?> FindAsync(string docId, bool withConflicts = false, CancellationToken cancellationToken = default)
78+
=> FindAsync(docId, new FindOptions { Conflicts = withConflicts }, cancellationToken);
79+
80+
/// <inheritdoc />
81+
public async Task<TSource?> FindAsync(string docId, FindOptions options, CancellationToken cancellationToken = default)
7882
{
7983
IFlurlRequest request = NewRequest()
8084
.AppendPathSegment(docId);
8185

82-
if (withConflicts)
83-
{
86+
if (options.Conflicts)
8487
request = request.SetQueryParam("conflicts", "true");
85-
}
88+
89+
if (options.Rev != null)
90+
request = request.SetQueryParam("rev", options.Rev);
8691

8792
IFlurlResponse? response = await request
8893
.AllowHttpStatus(HttpStatusCode.NotFound)
@@ -187,22 +192,24 @@ private void InitAttachments(TSource document)
187192
#region Writing
188193

189194
/// <inheritdoc />
190-
public async Task<TSource> AddAsync(TSource document, bool batch = false, CancellationToken cancellationToken = default)
195+
public Task<TSource> AddAsync(TSource document, bool batch = false, CancellationToken cancellationToken = default)
196+
=> AddAsync(document, new AddOptions { Batch = batch }, cancellationToken);
197+
198+
/// <inheritdoc />
199+
public async Task<TSource> AddAsync(TSource document, AddOptions options, CancellationToken cancellationToken = default)
191200
{
192201
Check.NotNull(document, nameof(document));
193202

194203
if (!string.IsNullOrEmpty(document.Id))
195204
{
196-
return await AddOrUpdateAsync(document, batch, cancellationToken)
205+
return await AddOrUpdateAsync(document, new AddOrUpdateOptions { Batch = options.Batch }, cancellationToken)
197206
.ConfigureAwait(false);
198207
}
199208

200209
IFlurlRequest request = NewRequest();
201210

202-
if (batch)
203-
{
211+
if (options.Batch)
204212
request = request.SetQueryParam("batch", "ok");
205-
}
206213

207214
document.SplitDiscriminator = _discriminator;
208215
DocumentSaveResponse response = await request
@@ -219,7 +226,11 @@ await UpdateAttachments(document, cancellationToken)
219226
}
220227

221228
/// <inheritdoc />
222-
public async Task<TSource> AddOrUpdateAsync(TSource document, bool batch = false, CancellationToken cancellationToken = default)
229+
public Task<TSource> AddOrUpdateAsync(TSource document, bool batch = false, CancellationToken cancellationToken = default)
230+
=> AddOrUpdateAsync(document, new AddOrUpdateOptions { Batch = batch }, cancellationToken);
231+
232+
/// <inheritdoc />
233+
public async Task<TSource> AddOrUpdateAsync(TSource document, AddOrUpdateOptions options, CancellationToken cancellationToken = default)
223234
{
224235
Check.NotNull(document, nameof(document));
225236

@@ -231,10 +242,11 @@ public async Task<TSource> AddOrUpdateAsync(TSource document, bool batch = false
231242
IFlurlRequest request = NewRequest()
232243
.AppendPathSegment(document.Id);
233244

234-
if (batch)
235-
{
245+
if (options.Batch)
236246
request = request.SetQueryParam("batch", "ok");
237-
}
247+
248+
if (options.Rev != null)
249+
request = request.SetQueryParam("rev", options.Rev);
238250

239251
document.SplitDiscriminator = _discriminator;
240252
DocumentSaveResponse response = await request
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace CouchDB.Driver.DatabaseApiMethodOptions
6+
{
7+
/// <summary>
8+
/// Options relevant to saving a new document (supported by both PUT /{db}/{docid} and POST /{db}).
9+
/// Check https://docs.couchdb.org/en/stable/api/database/common.html#post--db
10+
/// Check https://docs.couchdb.org/en/stable/api/document/common.html#put--db-docid
11+
/// </summary>
12+
public class AddOptions
13+
{
14+
/// <summary>
15+
/// Stores document in batch mode. Check https://docs.couchdb.org/en/stable/api/database/common.html#api-doc-batch-writes
16+
/// </summary>
17+
public bool Batch { get; set; } = false;
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace CouchDB.Driver.DatabaseApiMethodOptions
6+
{
7+
/// <summary>
8+
/// Options relevant to saving a document (supported by PUT HTTP-method).
9+
/// Check https://docs.couchdb.org/en/stable/api/document/common.html#put--db-docid
10+
/// </summary>
11+
public class AddOrUpdateOptions
12+
{
13+
/// <summary>
14+
/// Stores document in batch mode. Check https://docs.couchdb.org/en/stable/api/database/common.html#api-doc-batch-writes
15+
/// </summary>
16+
public bool Batch { get; set; } = false;
17+
18+
/// <summary>
19+
/// Document’s revision if updating an existing document.
20+
/// </summary>
21+
public string? Rev { get; set; }
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace CouchDB.Driver.DatabaseApiMethodOptions
6+
{
7+
/// <summary>
8+
/// Options relevant to getting a document (supported by GET HTTP-method).
9+
/// Check https://docs.couchdb.org/en/stable/api/document/common.html#get--db-docid
10+
/// </summary>
11+
public class FindOptions
12+
{
13+
/// <summary>
14+
/// Includes information about conflicts in document. Default is false
15+
/// </summary>
16+
public bool Conflicts { get; set; } = false;
17+
18+
/// <summary>
19+
/// Retrieves document of specified revision. Optional
20+
/// </summary>
21+
public string? Rev { get; set; }
22+
}
23+
}

src/CouchDB.Driver/ICouchDatabase.cs

+28
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using CouchDB.Driver.ChangesFeed.Responses;
99
using CouchDB.Driver.Indexes;
1010
using CouchDB.Driver.Local;
11+
using CouchDB.Driver.DatabaseApiMethodOptions;
1112
using CouchDB.Driver.Security;
1213
using CouchDB.Driver.Types;
1314
using CouchDB.Driver.Views;
@@ -31,6 +32,15 @@ public interface ICouchDatabase<TSource>: IOrderedQueryable<TSource>
3132
/// <returns>A task that represents the asynchronous operation. The task result contains the element found, or null.</returns>
3233
Task<TSource?> FindAsync(string docId, bool withConflicts = false, CancellationToken cancellationToken = default);
3334

35+
/// <summary>
36+
/// Finds the document with the given ID. If no document is found, then null is returned.
37+
/// </summary>
38+
/// <param name="docId">The document ID.</param>
39+
/// <param name="options">Set of options available for GET /{db}/{docid}</param>
40+
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
41+
/// <returns>A task that represents the asynchronous operation. The task result contains the element found, or null</returns>
42+
Task<TSource?> FindAsync(string docId, FindOptions options, CancellationToken cancellationToken = default);
43+
3444
/// <summary>
3545
/// Finds all documents matching the MangoQuery.
3646
/// </summary>
@@ -64,6 +74,15 @@ public interface ICouchDatabase<TSource>: IOrderedQueryable<TSource>
6474
/// <returns>A task that represents the asynchronous operation. The task result contains the element created.</returns>
6575
Task<TSource> AddAsync(TSource document, bool batch = false, CancellationToken cancellationToken = default);
6676

77+
/// <summary>
78+
/// Creates a new document and returns it.
79+
/// </summary>
80+
/// <param name="document">The document to create.</param>
81+
/// <param name="options">Set of options available for both PUT /{db}/{docid} and POST /{db}</param>
82+
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
83+
/// <returns>A task that represents the asynchronous operation. The task result contains the element created.</returns>
84+
Task<TSource> AddAsync(TSource document, AddOptions options, CancellationToken cancellationToken = default);
85+
6786
/// <summary>
6887
/// Creates or updates the document with the given ID.
6988
/// </summary>
@@ -73,6 +92,15 @@ public interface ICouchDatabase<TSource>: IOrderedQueryable<TSource>
7392
/// <returns>A task that represents the asynchronous operation. The task result contains the element created or updated.</returns>
7493
Task<TSource> AddOrUpdateAsync(TSource document, bool batch = false, CancellationToken cancellationToken = default);
7594

95+
/// <summary>
96+
/// Creates or updates the document with the given ID.
97+
/// </summary>
98+
/// <param name="document">The document to create or update</param>
99+
/// <param name="options">Set of options available for PUT /{db}/{docid}</param>
100+
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
101+
/// <returns>A task that represents the asynchronous operation. The task result contains the element created or updated.</returns>
102+
Task<TSource> AddOrUpdateAsync(TSource document, AddOrUpdateOptions options, CancellationToken cancellationToken = default);
103+
76104
/// <summary>
77105
/// Deletes the document with the given ID.
78106
/// </summary>
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
You need a CouchDB database engine running on http://localhost:5984/.
2+
3+
If you have a docker execute:
4+
```
5+
docker run -p 5984:5984 -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=admin -d couchdb
6+
```

tests/CouchDB.Driver.UnitTests/Database_Tests.cs

+81
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using CouchDB.Driver.UnitTests._Models;
1010
using CouchDB.Driver.Views;
1111
using Xunit;
12+
using CouchDB.Driver.DatabaseApiMethodOptions;
1213

1314
namespace CouchDB.Driver.UnitTests
1415
{
@@ -69,6 +70,44 @@ public async Task FindWithConflicts()
6970
.WithVerb(HttpMethod.Get);
7071
}
7172

73+
[Fact]
74+
public async Task FindWithOptionsRevision()
75+
{
76+
using var httpTest = new HttpTest();
77+
httpTest.RespondWithJson(new
78+
{
79+
_attachments = new Dictionary<string, object>
80+
{
81+
{ "luke.txt", new { ContentType = "text/plain" } }
82+
}
83+
});
84+
85+
var newR = await _rebels.FindAsync("1", new FindOptions { Rev = "1-xxx" });
86+
httpTest
87+
.ShouldHaveCalled("http://localhost/rebels/1*")
88+
.WithQueryParam("rev", "1-xxx")
89+
.WithVerb(HttpMethod.Get);
90+
}
91+
92+
[Fact]
93+
public async Task FindWithOptionsConflicts()
94+
{
95+
using var httpTest = new HttpTest();
96+
httpTest.RespondWithJson(new
97+
{
98+
_attachments = new Dictionary<string, object>
99+
{
100+
{ "luke.txt", new { ContentType = "text/plain" } }
101+
}
102+
});
103+
104+
var newR = await _rebels.FindAsync("1", new FindOptions { Conflicts = true });
105+
httpTest
106+
.ShouldHaveCalled("http://localhost/rebels/1*")
107+
.WithQueryParam("conflicts", "true")
108+
.WithVerb(HttpMethod.Get);
109+
}
110+
72111
[Fact]
73112
public async Task FindMany()
74113
{
@@ -128,6 +167,20 @@ public async Task Create()
128167
.WithVerb(HttpMethod.Post);
129168
}
130169

170+
[Fact]
171+
public async Task CreateWithOptionsBatch()
172+
{
173+
using var httpTest = new HttpTest();
174+
httpTest.RespondWithJson(new { Id = "xxx", Ok = true, Rev = "xxx" });
175+
176+
var r = new Rebel { Name = "Luke" };
177+
var newR = await _rebels.AddAsync(r, new AddOptions { Batch = true });
178+
httpTest
179+
.ShouldHaveCalled("http://localhost/rebels")
180+
.WithQueryParam("batch", "ok")
181+
.WithVerb(HttpMethod.Post);
182+
}
183+
131184
[Fact]
132185
public async Task CreateOrUpdate()
133186
{
@@ -141,6 +194,34 @@ public async Task CreateOrUpdate()
141194
.WithVerb(HttpMethod.Put);
142195
}
143196

197+
[Fact]
198+
public async Task CreateOrUpdateWithOptionsRevision()
199+
{
200+
using var httpTest = new HttpTest();
201+
httpTest.RespondWithJson(new { Id = "xxx", Ok = true, Rev = "2-xxx" });
202+
203+
var r = new Rebel { Name = "Luke", Id = "1" };
204+
var newR = await _rebels.AddOrUpdateAsync(r, new AddOrUpdateOptions { Rev = "1-xxx" });
205+
httpTest
206+
.ShouldHaveCalled("http://localhost/rebels/1")
207+
.WithQueryParam("rev", "1-xxx")
208+
.WithVerb(HttpMethod.Put);
209+
}
210+
211+
[Fact]
212+
public async Task CreateOrUpdateWithOptionsBatch()
213+
{
214+
using var httpTest = new HttpTest();
215+
httpTest.RespondWithJson(new { Id = "xxx", Ok = true, Rev = "2-xxx" });
216+
217+
var r = new Rebel { Name = "Luke", Id = "1" };
218+
var newR = await _rebels.AddOrUpdateAsync(r, new AddOrUpdateOptions { Batch = true });
219+
httpTest
220+
.ShouldHaveCalled("http://localhost/rebels/1")
221+
.WithQueryParam("batch", "ok")
222+
.WithVerb(HttpMethod.Put);
223+
}
224+
144225
[Fact]
145226
public async Task Create_Discriminator()
146227
{

0 commit comments

Comments
 (0)