Skip to content

Commit

Permalink
#102: Ability to create an index
Browse files Browse the repository at this point in the history
  • Loading branch information
matteobortolazzo committed Sep 14, 2020
1 parent 0c65adf commit 644fc43
Show file tree
Hide file tree
Showing 13 changed files with 367 additions and 51 deletions.
1 change: 1 addition & 0 deletions LATEST_CHANGE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## Features
* **Indexes"**: Ability to create indexes.
* **Null values"**: New `SetNullValueHandling` method for `CouchOptionsBuilder` to set how to handle null values.

## Bug Fixes
Expand Down
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
50 changes: 46 additions & 4 deletions src/CouchDB.Driver/CouchDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,22 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CouchDB.Driver.ChangesFeed;
using CouchDB.Driver.ChangesFeed.Responses;
using CouchDB.Driver.Indexes;
using CouchDB.Driver.Local;
using CouchDB.Driver.Options;
using CouchDB.Driver.Query;
using Flurl.Util;
using Newtonsoft.Json;

namespace CouchDB.Driver
Expand Down Expand Up @@ -374,7 +377,7 @@ public async Task<ChangesFeedResponse<TSource>> GetChangesAsync(ChangesFeedOptio
return filter == null
? await request.GetJsonAsync<ChangesFeedResponse<TSource>>(cancellationToken)
.ConfigureAwait(false)
: await request.QueryWithFilterAsync<TSource>(_options, filter, cancellationToken)
: await request.QueryWithFilterAsync<TSource>(_queryProvider, filter, cancellationToken)
.ConfigureAwait(false);
}

Expand All @@ -396,7 +399,7 @@ public async IAsyncEnumerable<ChangesFeedResponseResult<TSource>> GetContinuousC
await using Stream stream = filter == null
? await request.GetStreamAsync(cancellationToken, HttpCompletionOption.ResponseHeadersRead)
.ConfigureAwait(false)
: await request.QueryContinuousWithFilterAsync<TSource>(_options, filter, cancellationToken)
: await request.QueryContinuousWithFilterAsync<TSource>(_queryProvider, filter, cancellationToken)
.ConfigureAwait(false);

await foreach (var line in stream.ReadLinesAsync(cancellationToken))
Expand All @@ -413,6 +416,45 @@ public async IAsyncEnumerable<ChangesFeedResponseResult<TSource>> GetContinuousC

#endregion

#region Index

public async Task CreateIndexAsync(string name, Action<IIndexBuilder<TSource>> indexBuilderAction, IndexOptions? options = null)
{
Check.NotNull(name, nameof(name));
Check.NotNull(indexBuilderAction, nameof(indexBuilderAction));

var builder = new IndexBuilder<TSource>(_options, _queryProvider);
indexBuilderAction(builder);

var indexJson = builder.ToString();

var sb = new StringBuilder();
sb.Append("{")
.Append($"\"index\":{indexJson},")
.Append($"\"name\":\"{name}\",")
.Append("\"type\":\"json\"");

if (options?.DesignDocument != null)
{
sb.Append($",\"ddoc\":\"{options.DesignDocument}\"");
}
if (options?.Partitioned != null)
{
sb.Append($",\"partitioned\":{options.Partitioned.ToString().ToLowerInvariant()}");
}

sb.Append("}");

var request = sb.ToString();

await NewRequest().AppendPathSegment("_index")
.PostStringAsync(request)
.SendRequestAsync()
.ConfigureAwait(false);
}

#endregion

#region Utils

/// <inheritdoc />
Expand Down Expand Up @@ -495,4 +537,4 @@ internal CouchQueryable<TSource> AsQueryable()

#endregion
}
}
}
39 changes: 39 additions & 0 deletions src/CouchDB.Driver/Extensions/ExpressionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using CouchDB.Driver.Options;

namespace CouchDB.Driver.Extensions
{
internal static class ExpressionExtensions
{
public static MemberExpression ToMemberExpression(this Expression selector)
{
if (!(selector is LambdaExpression l) || !(l.Body is MemberExpression m))
{
throw new ArgumentException("The given expression does not select a property.", nameof(selector));
}

return m;
}

public static string GetPropertyName(this MemberExpression m, CouchOptions options)
{
PropertyCaseType caseType = options.PropertiesCase;

var members = new List<string> { m.Member.GetCouchPropertyName(caseType) };

Expression currentExpression = m.Expression;

while (currentExpression is MemberExpression cm)
{
members.Add(cm.Member.GetCouchPropertyName(caseType));
currentExpression = cm.Expression;
}

members.Reverse();
var propName = string.Join(".", members.ToArray());

return propName;
}

public static bool ContainsSelector(this Expression expression) =>
expression is MethodCallExpression m && m.Arguments.Count == 2 && m.Arguments[1].IsSelectorExpression();

Expand Down Expand Up @@ -39,6 +71,13 @@ public static Expression WrapInLambda(this Expression body, IReadOnlyCollection<
return Expression.Quote(lambdaExpression);
}

public static MethodCallExpression WrapInWhereExpression<TSource>(this Expression<Func<TSource, bool>> selector)
{
MethodCallExpression whereExpression = Expression.Call(typeof(Queryable), nameof(Queryable.Where),
new[] { typeof(TSource) }, Expression.Constant(Array.Empty<TSource>().AsQueryable()), selector);
return whereExpression;
}

private static Expression StripQuotes(this Expression e)
{
while (e.NodeType == ExpressionType.Quote)
Expand Down
1 change: 0 additions & 1 deletion src/CouchDB.Driver/Extensions/FlurlRequestExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ public static Task<Stream> PostStringStreamAsync(
return request.SendAsync(HttpMethod.Post, capturedStringContent, cancellationToken, completionOption).ReceiveStream();
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "<Pending>")]
public static IFlurlRequest ApplyQueryParametersOptions(this IFlurlRequest request, object options)
{
IEnumerable<(string Name, object? Value)> queryParameters = OptionsHelper.ToQueryParameters(options);
Expand Down
16 changes: 14 additions & 2 deletions src/CouchDB.Driver/ICouchDatabase.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CouchDB.Driver.ChangesFeed;
using CouchDB.Driver.ChangesFeed.Responses;
using CouchDB.Driver.Indexes;
using CouchDB.Driver.Local;
using CouchDB.Driver.Security;
using CouchDB.Driver.Types;
Expand Down Expand Up @@ -124,7 +126,17 @@ IAsyncEnumerable<ChangesFeedResponseResult<TSource>> GetContinuousChangesAsync(
CancellationToken cancellationToken);

/// <summary>
/// Asynchronously downloads a specific attachment.
/// Creates an index for the current database with the given configuration.
/// </summary>
/// <param name="name">The name of the index.</param>
/// <param name="indexBuilderAction">The action to configure the index builder.</param>
/// <param name="options">The index options.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
Task CreateIndexAsync(string name, Action<IIndexBuilder<TSource>> indexBuilderAction,
IndexOptions? options = null);

/// <summary>
/// Asynchronously downloads a specific attachment.
/// </summary>
/// <param name="attachment">The attachment to download.</param>
/// <param name="localFolderPath">Path of local folder where file is to be downloaded.</param>
Expand Down
35 changes: 35 additions & 0 deletions src/CouchDB.Driver/Indexes/IIndexBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Linq.Expressions;
using CouchDB.Driver.Types;

namespace CouchDB.Driver.Indexes
{
public interface IIndexBuilder<TSource>
where TSource : CouchDocument
{
IMultiFieldIndexBuilder<TSource> IndexBy<TSelector>(Expression<Func<TSource, TSelector>> selector);
}

public interface IMultiFieldIndexBuilder<TSource> : IIndexBuilder<TSource>
where TSource : CouchDocument
{
IMultiFieldIndexBuilder<TSource> AlsoBy<TSelector>(Expression<Func<TSource, TSelector>> selector);
IMultiFieldIndexBuilder<TSource> Where(Expression<Func<TSource, bool>> selector);
IOrderedIndexBuilder<TSource> OrderBy<TSelector>(Expression<Func<TSource, TSelector>> selector);
IOrderedDescendingIndexBuilder<TSource> OrderByDescending<TSelector>(Expression<Func<TSource, TSelector>> selector);
IMultiFieldIndexBuilder<TSource> Take(int take);
IMultiFieldIndexBuilder<TSource> Skip(int skip);
}

public interface IOrderedIndexBuilder<TSource> : IMultiFieldIndexBuilder<TSource>
where TSource : CouchDocument
{
IOrderedIndexBuilder<TSource> ThenBy<TSelector>(Expression<Func<TSource, TSelector>> selector);
}

public interface IOrderedDescendingIndexBuilder<TSource> : IMultiFieldIndexBuilder<TSource>
where TSource : CouchDocument
{
IOrderedDescendingIndexBuilder<TSource> ThenByDescending<TSelector>(Expression<Func<TSource, TSelector>> selector);
}
}
Loading

0 comments on commit 644fc43

Please sign in to comment.