Skip to content
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

Advanced query index #480

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
24 changes: 15 additions & 9 deletions src/YesSql.Abstractions/IQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ public interface IQuery
/// <typeparam name="T">The type of index to return</typeparam>
IQueryIndex<T> ForIndex<T>() where T : class, IIndex;

/// <summary>
/// Defines what type of index should be returned
/// </summary>
/// <typeparam name="T">The type of index to return</typeparam>
IQuery<T> ForAdvancedIndex<T>() where T : class, IIndex;

/// <summary>
/// Returns documents from any type
/// </summary>
Expand Down Expand Up @@ -63,13 +69,13 @@ public interface IQuery<T> where T : class
/// </summary>
/// <typeparam name="TIndex">The index to filter on.</typeparam>
IQuery<T, TIndex> With<TIndex>() where TIndex : class, IIndex;

/// <summary>
/// Filters the documents with a constraint on the specified index.
/// </summary>
/// <typeparam name="TIndex">The index to filter on.</typeparam>
IQuery<T, TIndex> With<TIndex>(Expression<Func<TIndex, bool>> predicate) where TIndex : class, IIndex;

/// <summary>
/// Skips the specified number of document.
/// </summary>
Expand Down Expand Up @@ -128,7 +134,7 @@ public interface IQueryIndex<T> where T : IIndex
/// Joins the document table with an index, and filter it with a predicate.
/// </summary>
IQueryIndex<TIndex> With<TIndex>(Expression<Func<TIndex, bool>> predicate) where TIndex : class, IIndex;

/// <summary>
/// Adds a custom Where clause to the query.
/// </summary>
Expand Down Expand Up @@ -163,7 +169,7 @@ public interface IQueryIndex<T> where T : IIndex
/// Adds an OrderBy clause using a custom lambda expression.
/// </summary>
IQueryIndex<T> ThenBy(Expression<Func<T, object>> keySelector);

/// <summary>
/// Adds a descending OrderBy clause using a custom lambda expression.
/// </summary>
Expand Down Expand Up @@ -221,7 +227,7 @@ public interface IQuery<T, TIndex> : IQuery<T>
/// Adds a custom Where clause to the query using a specific dialect.
/// </summary>
IQuery<T, TIndex> Where(Func<ISqlDialect, string> sql);

/// <summary>
/// Adds a named parameter to the query.
/// </summary>
Expand All @@ -236,19 +242,19 @@ public interface IQuery<T, TIndex> : IQuery<T>
/// Sets an OrderBy clause using a custom lambda expression.
/// </summary>
IQuery<T, TIndex> OrderBy(Expression<Func<TIndex, object>> keySelector);

/// <summary>
/// Sets an OrderBy clause using a custom SQL statement.
/// </summary>
IQuery<T, TIndex> OrderBy(string sql);

IQuery<T, TIndex> OrderByDescending(Expression<Func<TIndex, object>> keySelector);

/// <summary>
/// Sets a descending OrderBy clause using a custom SQL statement.
/// </summary>
IQuery<T, TIndex> OrderByDescending(string sql);

/// <summary>
/// Sets a random OrderBy clause.
/// </summary>
Expand Down
8 changes: 8 additions & 0 deletions src/YesSql.Abstractions/QueryExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ public static IQueryIndex<TIndex> QueryIndex<TIndex>(this ISession session, stri
return session.Query(collection).ForIndex<TIndex>();
}

/// <summary>
/// Creates a query on an index.
/// </summary>
public static IQuery<TIndex> AdvancedQueryIndex<TIndex>(this ISession session, string collection = null) where TIndex : class, IIndex
{
return session.Query(collection).ForAdvancedIndex<TIndex>();
}

/// <summary>
/// Creates a query on an index, with a predicate.
/// </summary>
Expand Down
52 changes: 40 additions & 12 deletions src/YesSql.Core/Services/DefaultQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,13 @@ public QueryState Clone()
clone._bindings.Add(binding.Key, new List<Type>(binding.Value));
}

clone._currentPredicate = (CompositeNode) _predicate.Clone();
clone._currentPredicate = (CompositeNode)_predicate.Clone();
clone._predicate = clone._currentPredicate;
clone._deduplicate = _deduplicate;

clone._lastParameterName = _lastParameterName;
clone._parameterBindings = _parameterBindings == null ? null : new List<Action<object, ISqlBuilder>>(_parameterBindings);

return clone;
}
}
Expand Down Expand Up @@ -260,7 +260,7 @@ static DefaultQuery()
var objects = Expression.Lambda(expression.Arguments[1]).Compile().DynamicInvoke() as IEnumerable;
var values = new List<object>();

foreach(var o in objects)
foreach (var o in objects)
{
values.Add(o);
}
Expand All @@ -272,7 +272,7 @@ static DefaultQuery()
else if (values.Count == 1)
{
query.ConvertFragment(builder, expression.Arguments[0]);
builder.Append(" = " );
builder.Append(" = ");
query.ConvertFragment(builder, Expression.Constant(values[0]));
}
else
Expand Down Expand Up @@ -440,7 +440,7 @@ private void Bind(Type tIndex)
if (bindingIndex != -1)
{
// When a binding is reused it should be last to be correctly applied to a filter predicate.
if (bindingIndex != bindings.Count -1)
if (bindingIndex != bindings.Count - 1)
{
var binding = bindings[bindingIndex];
bindings.RemoveAt(bindingIndex);
Expand Down Expand Up @@ -685,13 +685,13 @@ public void ConvertFragment(IStringBuilder builder, Expression expression)
_queryState._lastParameterName = "@p" + _queryState._sqlBuilder.Parameters.Count.ToString();
_queryState._sqlBuilder.Parameters.Add(_queryState._lastParameterName, _dialect.TryConvert(left.Value));
builder.Append(_queryState._lastParameterName);

builder.Append(GetBinaryOperator(expression));

_queryState._lastParameterName = "@p" + _queryState._sqlBuilder.Parameters.Count.ToString();
_queryState._sqlBuilder.Parameters.Add(_queryState._lastParameterName, _dialect.TryConvert(right.Value));
builder.Append(_queryState._lastParameterName);

return;
}

Expand Down Expand Up @@ -760,7 +760,7 @@ public void ConvertFragment(IStringBuilder builder, Expression expression)
var boundTable = _queryState.GetTypeAlias(bound);
builder.Append(_queryState._sqlBuilder.FormatColumn(boundTable, memberExpression.Member.Name, _queryState._store.Configuration.Schema, true));
}

break;
case ExpressionType.Constant:
_queryState._lastParameterName = "@p" + _queryState._sqlBuilder.Parameters.Count.ToString();
Expand All @@ -772,7 +772,7 @@ public void ConvertFragment(IStringBuilder builder, Expression expression)
var methodCallExpression = (MethodCallExpression)expression;
var methodInfo = methodCallExpression.Method;
Action<DefaultQuery, IStringBuilder, ISqlDialect, MethodCallExpression> action;
if (MethodMappings.TryGetValue(methodInfo, out action)
if (MethodMappings.TryGetValue(methodInfo, out action)
|| MethodMappings.TryGetValue(methodInfo.GetGenericMethodDefinition(), out action))
{
action(this, builder, _dialect, methodCallExpression);
Expand Down Expand Up @@ -1113,6 +1113,34 @@ IQueryIndex<TIndex> IQuery.ForIndex<TIndex>()
return new QueryIndex<TIndex>(this);
}

IQuery<TIndex> IQuery.ForAdvancedIndex<TIndex>()
{
_queryState.GetBindings().Clear();

var tIndex = typeof(TIndex);

var indexTable = _queryState._store.Configuration.TableNameConvention.GetIndexTable(tIndex, _collection);
var indexTableAlias = _queryState.GetTypeAlias(tIndex);

_queryState.AddBinding(tIndex);
_queryState._sqlBuilder.Select();

_queryState._sqlBuilder.Table(indexTable, indexTableAlias, _queryState._store.Configuration.Schema);

_queryState.AddBinding(typeof(Document));

if (typeof(MapIndex).IsAssignableFrom(tIndex))
{
_queryState._sqlBuilder.InnerJoin(_queryState._documentTable, indexTableAlias, "DocumentId", _queryState._documentTable, _queryState._store.Configuration.Schema, "Id");
}
else
{
throw new InvalidOperationException("Reduce Indexes are not supported");
}

return new Query<TIndex>(this);
}

IQuery<object> IQuery.Any()
{
_queryState.GetBindings().Clear();
Expand Down Expand Up @@ -1226,7 +1254,7 @@ Task<IEnumerable<T>> IQuery<T>.ListAsync()
async IAsyncEnumerable<T> IQuery<T>.ToAsyncEnumerable()
{
// TODO: [IAsyncEnumerable] Once Dapper supports IAsyncEnumerable we can replace this call by a non-buffered one
foreach(var item in await ListImpl())
foreach (var item in await ListImpl())
{
yield return item;
}
Expand Down Expand Up @@ -1291,7 +1319,7 @@ internal async Task<IEnumerable<T>> ListImpl()
sqlBuilder.Selector(sqlBuilder.FormatColumn(_query._queryState._documentTable, "*", schema));

// Group by document id to de-duplicate records if the index has multiple matches for a single document

// TODO: This could potentially be detected automically, for instance by creating a MultiMapIndex, but might require breaking changes

var sql = _query._queryState._deduplicate ? GetDeduplicatedQuery() : sqlBuilder.ToSqlString();
Expand Down Expand Up @@ -1472,7 +1500,7 @@ private async ValueTask<IQuery<T>> ComposeQueryAsync(Func<IQuery<T>, ValueTask<I
}

return new Query<T>(_query);
}
}

IQuery<T, TIndex> IQuery<T>.With<TIndex>()
{
Expand Down
47 changes: 46 additions & 1 deletion test/YesSql.Tests/CoreTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public async Task InitializeAsync()
if (_configuration == null)
{
_configuration = CreateConfiguration();

CleanDatabase(_configuration, false);

_store = await StoreFactory.CreateAndInitializeAsync(_configuration);
Expand Down Expand Up @@ -1583,6 +1583,51 @@ public async Task ShouldCreateSeveralMapIndexPerDocument()
}
}

[Fact]
public async Task ShouldAdvancedQueryMultipleIndexes()
{
// We should be able to query documents on multiple rows in multiple index
// This mean the same Index table needs to be JOINed

_store.RegisterIndexes<PersonIdentitiesIndexProvider>();
_store.RegisterIndexes<PersonByNullableAgeIndexProvider>();

using (var session = _store.CreateSession())
{
var hanselman = new Person
{
Firstname = "Scott",
Lastname = "Hanselman",
Age = -1
};

var guthrie = new Person
{
Firstname = "Scott",
Lastname = "Guthrie"
};

session.Save(hanselman);
session.Save(guthrie);

await session.SaveChangesAsync();
}

using (var session = _store.CreateSession())
{
Assert.Equal(2, await session.AdvancedQueryIndex<PersonIdentity>()
.All(
x => x.With<PersonByNullableAge>(x => x.Age == null || x.Age == 0),
x => x.Any(
x => x.With<PersonIdentity>(x => x.Identity == "Hanselman"),
x => x.With<PersonIdentity>(x => x.Identity == "Guthrie"))
)
.CountAsync()
);
}
}


[Fact]
public async Task ShouldQueryMultipleIndexes()
{
Expand Down