Skip to content

v2.1.0 #105

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 18 commits into from
Sep 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions LATEST_CHANGE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## Features
* **Users"**: New `ChangeUserPassword` mathod for `ICouchDatabase<CouchUser>`.
* **Indexes"**: Ability to create indexes. ([#102](https://github.com/matteobortolazzo/couchdb-net/issues/102))
* **Null values"**: New `SetNullValueHandling` method for `CouchOptionsBuilder` to set how to handle null values. ([#101](https://github.com/matteobortolazzo/couchdb-net/issues/101))
* **Query"**: New `Select` and `Convert` methods to select specific fields.

## Bug Fixes
* **IsMatch**: Back to public instead of internal;
* **AddOrUpdate**: Added `Async` postfix.
* **Conflicts**: Fix the query parameter value to get conflicts. ([#100](https://github.com/matteobortolazzo/couchdb-net/issues/100))
* **Query**: Fix queries when variables are used. ([#104](https://github.com/matteobortolazzo/couchdb-net/issues/104))
90 changes: 86 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ var skywalkers = await context.Rebels
.OrderByDescending(r => r.Name)
.ThenByDescending(r => r.Age)
.Take(2)
.Select(r => new {
r.Name,
r.Age
.Select(
r => r.Name,
r => r.Age
})
.ToListAsync();
```
Expand Down Expand Up @@ -101,6 +101,9 @@ The produced Mango JSON:
* [DB Changes Feed](#db-changes-feed)
* [Feed Options](#feed-options)
* [Feed Filter](#feed-filter)
* [Indexing](#indexing)
* [Index Options](#index-options)
* [Partial Indexes](#partial-indexes)
* [Local (non-replicating) Documents](#local-(non-replicating)-documents)
* [Bookmark and Execution stats](#bookmark-and-execution-stats)
* [Users](#users)
Expand Down Expand Up @@ -192,7 +195,8 @@ If the Where method is not called in the expression, it will at an empty selecto
| sort | OrderBy(..).ThenBy() |
| sort | OrderByDescending(..) |
| sort | OrderByDescending(..).ThenByDescending() |
| fields | Select(x => new { }) |
| fields | Select(x => x.Prop1, x => x.Prop2) |
| fields | Convert<SourceType, SimplerType>() |
| use_index | UseIndex("design_document") |
| use_index | UseIndex(new [] { "design_document", "index_name" }) |
| r | WithReadQuorum(n) |
Expand Down Expand Up @@ -331,6 +335,7 @@ var client = new CouchClient("http://localhost:5984", builder => builder
| DisableDocumentPluralization | Disables documents pluralization in requests. |
| SetDocumentCase | Sets the format case for documents. |
| SetPropertyCase | Sets the format case for properties. |
| SetNullValueHandling | Sets how to handle null values. |
| DisableLogOutOnDispose | Disables log out on client dispose. |

- **DocumentCaseTypes**: None, UnderscoreCase *(default)*, DashCase, KebabCase.
Expand Down Expand Up @@ -426,6 +431,81 @@ var filter = ChangesFeedFilter.View(view);
ChangesFeedResponse<Rebel> changes = await GetChangesAsync(options: null, filter);
```

## Indexing

It is possible to create indexes to use when querying.

```csharp
// Basic index creation
await _rebels.CreateIndexAsync("rebels_index", b => b
.IndexBy(r => r.Surname))
.ThenBy(r => r.Name));

// Descending index creation
await _rebels.CreateIndexAsync("rebels_index", b => b
.IndexByDescending(r => r.Surname))
.ThenByDescending(r => r.Name));
```

### Index Options

```csharp
// Specifies the design document and/or whether a JSON index is partitioned or global
await _rebels.CreateIndexAsync("rebels_index", b => b
.IndexBy(r => r.Surname),
new IndexOptions()
{
DesignDocument = "surnames_ddoc",
Partitioned = true
});
```

### Partial Indexes
```csharp
// Create an index which excludes documents at index time
await _rebels.CreateIndexAsync("skywalkers_index", b => b
.IndexBy(r => r.Name)
.Where(r => r.Surname == "Skywalker");
```

### Indexes operations
```csharp
// Get the list of indexes
var indexes = await _rebels.GetIndexesAsync();

// Delete an indexes
await _rebels.DeleteIndexAsync(indexes[0]);
// or
await _rebels.DeleteIndexAsync("surnames_ddoc", name: "surnames");
```

### CouchContext Index Configuration

Finally it's possible to configure indexes on the `CouchContext`.
```csharp
public class MyDeathStarContext : CouchContext
{
public CouchDatabase<Rebel> Rebels { get; set; }

protected override void OnConfiguring(CouchOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseEndpoint("http://localhost:5984/")
.UseBasicAuthentication("admin", "admin")
// If it finds a index with the same name and ddoc (or null)
// but with different fields and/or sort order,
// it will override the index
.OverrideExistingIndexes();
}

protected override void OnDatabaseCreating(CouchDatabaseBuilder databaseBuilder)
{
databaseBuilder.Document<Rebel>()
.HasIndex("rebel_surnames_index", b => b.IndexBy(b => b.Surname));
}
}
```

## Local (non-replicating) Documents

The Local (non-replicating) document interface allows you to create local documents that are not replicated to other databases.
Expand Down Expand Up @@ -548,3 +628,5 @@ Also, the configurator has `ConfigureFlurlClient` to set custom HTTP client opti
Thanks to [Ben Origas](https://github.com/borigas) for features, ideas and tests like SSL custom validation, multi queryable, async deadlock, cookie authenication and many others.

Thanks to [n9](https://github.com/n9) for proxy authentication, some bug fixes, suggestions and the great feedback on the changes feed feature!

Thanks to [Marc](https://github.com/bender-ristone) for NullValueHandling, bug fixes and suggestions!
3 changes: 3 additions & 0 deletions src/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,6 @@ csharp_preserve_single_line_statements = true

# CA1303: Do not pass literals as localized parameters
dotnet_diagnostic.CA1303.severity = silent

# CA1308: Normalize strings to uppercase
dotnet_diagnostic.CA1308.severity = suggestion
20 changes: 6 additions & 14 deletions src/CouchDB.Driver/ChangesFeed/ChangesFeedFilterExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
using System;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using CouchDB.Driver.ChangesFeed.Filters;
using CouchDB.Driver.ChangesFeed.Responses;
using CouchDB.Driver.Extensions;
using CouchDB.Driver.Options;
using CouchDB.Driver.Query;
using CouchDB.Driver.Types;
using Flurl.Http;
Expand All @@ -17,7 +15,7 @@ namespace CouchDB.Driver.ChangesFeed
{
internal static class ChangesFeedFilterExtensions
{
public static async Task<ChangesFeedResponse<TSource>> QueryWithFilterAsync<TSource>(this IFlurlRequest request, CouchOptions options, ChangesFeedFilter filter,
public static async Task<ChangesFeedResponse<TSource>> QueryWithFilterAsync<TSource>(this IFlurlRequest request, IAsyncQueryProvider queryProvider, ChangesFeedFilter filter,
CancellationToken cancellationToken)
where TSource : CouchDocument
{
Expand All @@ -32,12 +30,9 @@ public static async Task<ChangesFeedResponse<TSource>> QueryWithFilterAsync<TSou

if (filter is SelectorChangesFeedFilter<TSource> selectorFilter)
{
MethodCallExpression whereExpression = Expression.Call(typeof(Queryable), nameof(Queryable.Where),
new[] { typeof(TSource) }, Expression.Constant(Array.Empty<TSource>().AsQueryable()), selectorFilter.Value);
MethodCallExpression whereExpression = selectorFilter.Value.WrapInWhereExpression();
var jsonSelector = queryProvider.ToString(whereExpression);

var optimizer = new QueryOptimizer();
Expression optimizedQuery = optimizer.Optimize(whereExpression);
var jsonSelector = new QueryTranslator(options).Translate(optimizedQuery);
return await request
.WithHeader("Content-Type", "application/json")
.SetQueryParam("filter", "_selector")
Expand Down Expand Up @@ -65,7 +60,7 @@ public static async Task<ChangesFeedResponse<TSource>> QueryWithFilterAsync<TSou
throw new InvalidOperationException($"Filter of type {filter.GetType().Name} not supported.");
}

public static async Task<Stream> QueryContinuousWithFilterAsync<TSource>(this IFlurlRequest request, CouchOptions options, ChangesFeedFilter filter, CancellationToken cancellationToken)
public static async Task<Stream> QueryContinuousWithFilterAsync<TSource>(this IFlurlRequest request, IAsyncQueryProvider queryProvider, ChangesFeedFilter filter, CancellationToken cancellationToken)
where TSource: CouchDocument
{
if (filter is DocumentIdsChangesFeedFilter documentIdsFilter)
Expand All @@ -78,12 +73,9 @@ public static async Task<Stream> QueryContinuousWithFilterAsync<TSource>(this IF

if (filter is SelectorChangesFeedFilter<TSource> selectorFilter)
{
MethodCallExpression whereExpression = Expression.Call(typeof(Queryable), nameof(Queryable.Where),
new[] { typeof(TSource) }, Expression.Constant(Array.Empty<TSource>().AsQueryable()), selectorFilter.Value);
MethodCallExpression whereExpression = selectorFilter.Value.WrapInWhereExpression();
var jsonSelector = queryProvider.ToString(whereExpression);

var optimizer = new QueryOptimizer();
Expression optimizedQuery = optimizer.Optimize(whereExpression);
var jsonSelector = new QueryTranslator(options).Translate(optimizedQuery);
return await request
.WithHeader("Content-Type", "application/json")
.SetQueryParam("filter", "_selector")
Expand Down
3 changes: 2 additions & 1 deletion src/CouchDB.Driver/CouchClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ private IFlurlClient GetConfiguredClient() =>
{
s.JsonSerializer = new NewtonsoftJsonSerializer(new JsonSerializerSettings
{
ContractResolver = new CouchContractResolver(_options.PropertiesCase)
ContractResolver = new CouchContractResolver(_options.PropertiesCase),
NullValueHandling = _options.NullValueHandling ?? NullValueHandling.Include
});
s.BeforeCallAsync = OnBeforeCallAsync;
if (_options.ServerCertificateCustomValidationCallback != null)
Expand Down
Loading