Skip to content

Commit af22327

Browse files
Merge pull request #126 from panoukos41/feature/view_queries
The example is correct, I can merge this one
2 parents eba7edd + 68f5736 commit af22327

File tree

5 files changed

+224
-0
lines changed

5 files changed

+224
-0
lines changed

README.md

+27
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,33 @@ var viewRows = await _rebels.GetViewAsync<string[], RebelView>("jedi", "by_name"
541541
var details = await _rebels.GetDetailedViewAsync<int, BattleView>("battle", "by_name", options);
542542
```
543543

544+
You can also query a view with multiple options to get multiple results:
545+
```csharp
546+
var lukeOptions = new CouchViewOptions<string[]>
547+
{
548+
Key = new[] {"Luke", "Skywalker"},
549+
IncludeDocs = true
550+
};
551+
var yodaOptions = new CouchViewOptions<string[]>
552+
{
553+
Key = new[] {"Yoda"},
554+
IncludeDocs = true
555+
};
556+
var queries = new[]
557+
{
558+
lukeOptions,
559+
yodaOptions
560+
};
561+
562+
var results = await _rebels.GetViewQueryAsync<string[], RebelView>("jedi", "by_name", queries);
563+
var lukeRows = results[0];
564+
var yodaRows = results[1];
565+
// OR
566+
var details = await _rebels.GetDetailedViewQueryAsync<string[], RebelView>("jedi", "by_name", queries);
567+
var lukeDetails = details[0];
568+
var yodaDetails = details[1];
569+
```
570+
544571
## Local (non-replicating) Documents
545572

546573
The Local (non-replicating) document interface allows you to create local documents that are not replicated to other databases.

src/CouchDB.Driver/CouchDatabase.cs

+32
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,38 @@ public Task<CouchViewList<TKey, TValue, TSource>> GetDetailedViewAsync<TKey, TVa
536536
return requestTask.SendRequestAsync();
537537
}
538538

539+
/// <inheritdoc/>
540+
public async Task<List<CouchView<TKey, TValue, TSource>>[]> GetViewQueryAsync<TKey, TValue>(string design, string view,
541+
IList<CouchViewOptions<TKey>> queries, CancellationToken cancellationToken = default)
542+
{
543+
CouchViewList<TKey, TValue, TSource>[] result =
544+
await GetDetailedViewQueryAsync<TKey, TValue>(design, view, queries, cancellationToken)
545+
.ConfigureAwait(false);
546+
547+
return result.Select(x => x.Rows).ToArray();
548+
}
549+
550+
/// <inheritdoc/>
551+
public async Task<CouchViewList<TKey, TValue, TSource>[]> GetDetailedViewQueryAsync<TKey, TValue>(string design, string view,
552+
IList<CouchViewOptions<TKey>> queries, CancellationToken cancellationToken = default)
553+
{
554+
Check.NotNull(design, nameof(design));
555+
Check.NotNull(view, nameof(view));
556+
Check.NotNull(queries, nameof(queries));
557+
558+
IFlurlRequest request = NewRequest()
559+
.AppendPathSegments("_design", design, "_view", view, "queries");
560+
561+
CouchViewQueryResult<TKey, TValue, TSource> result =
562+
await request
563+
.PostJsonAsync(new { queries }, cancellationToken)
564+
.ReceiveJson<CouchViewQueryResult<TKey, TValue, TSource>>()
565+
.SendRequestAsync()
566+
.ConfigureAwait(false);
567+
568+
return result.Results;
569+
}
570+
539571
#endregion
540572

541573
#region Utils
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using CouchDB.Driver.Types;
2+
using CouchDB.Driver.Views;
3+
using Newtonsoft.Json;
4+
using System.Collections.Generic;
5+
6+
#nullable disable
7+
namespace CouchDB.Driver.DTOs
8+
{
9+
internal class CouchViewQueryResult<TKey, TValue, TDoc>
10+
where TDoc : CouchDocument
11+
{
12+
/// <summary>
13+
/// The results in the same order as the queries.
14+
/// </summary>
15+
[JsonProperty("results")]
16+
public CouchViewList<TKey, TValue, TDoc>[] Results { get; set; }
17+
}
18+
}
19+
#nullable restore

src/CouchDB.Driver/ICouchDatabase.cs

+28
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,34 @@ Task<List<CouchView<TKey, TValue, TSource>>> GetViewAsync<TKey, TValue>(string d
115115
Task<CouchViewList<TKey, TValue, TSource>> GetDetailedViewAsync<TKey, TValue>(string design, string view,
116116
CouchViewOptions<TKey>? options = null, CancellationToken cancellationToken = default);
117117

118+
/// <summary>
119+
/// Executes the specified view function from the specified design document using
120+
/// the queries endpoint. This returns one result for each query option in the provided sequence.
121+
/// </summary>
122+
/// <typeparam name="TKey">The type of the key.</typeparam>
123+
/// <typeparam name="TValue">The type of the value.</typeparam>
124+
/// <param name="design">The design to use.</param>
125+
/// <param name="view">The view to use.</param>
126+
/// <param name="queries">Multiple query options for the request.</param>
127+
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
128+
/// <returns>A task that represents the asynchronous operation. The task result contains an array with a list of <see cref="CouchView{TKey, TValue, TSource}"/> for each query.</returns>
129+
Task<List<CouchView<TKey, TValue, TSource>>[]> GetViewQueryAsync<TKey, TValue>(string design, string view,
130+
IList<CouchViewOptions<TKey>> queries, CancellationToken cancellationToken = default);
131+
132+
/// <summary>
133+
/// Executes the specified view function from the specified design document using
134+
/// the queries endpoint. This returns one result for each query option in the provided sequence.
135+
/// </summary>
136+
/// <typeparam name="TKey">The type of the key.</typeparam>
137+
/// <typeparam name="TValue">The type of the value.</typeparam>
138+
/// <param name="design">The design to use.</param>
139+
/// <param name="view">The view to use.</param>
140+
/// <param name="queries">Multiple query options for the request.</param>
141+
/// <param name="cancellationToken">A <see cref="CancellationToken" /> to observe while waiting for the task to complete.</param>
142+
/// <returns>A task that represents the asynchronous operation. The task result contains a list with a <see cref="CouchViewList{TKey, TSource, TView}"/> for each query.</returns>
143+
Task<CouchViewList<TKey, TValue, TSource>[]> GetDetailedViewQueryAsync<TKey, TValue>(string design, string view,
144+
IList<CouchViewOptions<TKey>> queries, CancellationToken cancellationToken = default);
145+
118146
/// <summary>
119147
/// Since CouchDB v3, it is deprecated (a no-op).
120148
///

tests/CouchDB.Driver.UnitTests/Database_Tests.cs

+118
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,124 @@ private static void SetupViewResponse(HttpTest httpTest)
371371
});
372372
}
373373

374+
[Fact]
375+
public async Task GetViewQueryAsync()
376+
{
377+
// Arrange
378+
using var httpTest = new HttpTest();
379+
SetupViewQueryResponse(httpTest);
380+
var options = new CouchViewOptions<string[]>
381+
{
382+
Key = new[] {"Luke", "Skywalker"},
383+
Skip = 10
384+
};
385+
var queries = new[]
386+
{
387+
options,
388+
options
389+
};
390+
391+
// Act
392+
var results = await _rebels.GetViewQueryAsync<string[], RebelView>("jedi", "by_name", queries);
393+
394+
// Assert
395+
Assert.Equal(2, results.Length);
396+
397+
Assert.All(results, result =>
398+
{
399+
var rebel = Assert.Single(result);
400+
Assert.Equal("luke", rebel.Id);
401+
Assert.Equal(new[] { "Luke", "Skywalker" }, rebel.Key);
402+
Assert.Equal(3, rebel.Value.NumberOfBattles);
403+
});
404+
httpTest
405+
.ShouldHaveCalled("http://localhost/rebels/_design/jedi/_view/by_name/queries")
406+
.WithVerb(HttpMethod.Post)
407+
.WithRequestBody(@"{""queries"":[{""key"":[""Luke"",""Skywalker""],""skip"":10},{""key"":[""Luke"",""Skywalker""],""skip"":10}]}");
408+
}
409+
410+
[Fact]
411+
public async Task GetDetailedViewQueryAsync()
412+
{
413+
// Arrange
414+
using var httpTest = new HttpTest();
415+
SetupViewQueryResponse(httpTest);
416+
var options = new CouchViewOptions<string[]>
417+
{
418+
Key = new[] {"Luke", "Skywalker"},
419+
Skip = 10
420+
};
421+
var queries = new[]
422+
{
423+
options,
424+
options
425+
};
426+
427+
// Act
428+
var results = await _rebels.GetDetailedViewQueryAsync<string[], RebelView>("jedi", "by_name", queries);
429+
430+
// Assert
431+
Assert.Equal(2, results.Length);
432+
433+
Assert.All(results, result =>
434+
{
435+
Assert.Equal(10, result.Offset);
436+
Assert.Equal(20, result.TotalRows);
437+
var rebel = Assert.Single(result.Rows);
438+
Assert.Equal("luke", rebel.Id);
439+
Assert.Equal(new[] { "Luke", "Skywalker" }, rebel.Key);
440+
Assert.Equal(3, rebel.Value.NumberOfBattles);
441+
});
442+
httpTest
443+
.ShouldHaveCalled("http://localhost/rebels/_design/jedi/_view/by_name/queries")
444+
.WithVerb(HttpMethod.Post)
445+
.WithRequestBody(@"{""queries"":[{""key"":[""Luke"",""Skywalker""],""skip"":10},{""key"":[""Luke"",""Skywalker""],""skip"":10}]}");
446+
}
447+
448+
private static void SetupViewQueryResponse(HttpTest httpTest)
449+
{
450+
httpTest.RespondWithJson(new
451+
{
452+
Results = new[]
453+
{
454+
new
455+
{
456+
Offset = 10,
457+
Total_Rows = 20,
458+
Rows = new[]
459+
{
460+
new
461+
{
462+
Id = "luke",
463+
Key = new [] {"Luke", "Skywalker"},
464+
Value = new
465+
{
466+
NumberOfBattles = 3
467+
}
468+
}
469+
}
470+
},
471+
new
472+
{
473+
Offset = 10,
474+
Total_Rows = 20,
475+
Rows = new[]
476+
{
477+
new
478+
{
479+
Id = "luke",
480+
Key = new [] {"Luke", "Skywalker"},
481+
Value = new
482+
{
483+
NumberOfBattles = 3
484+
}
485+
}
486+
}
487+
}
488+
}
489+
});
490+
}
491+
374492
#endregion
375493

376494
#region Utils

0 commit comments

Comments
 (0)