From 9ddb8f8655b8ce51b1ba33781aaafa957e22f0e7 Mon Sep 17 00:00:00 2001 From: Michael Petrinolis Date: Mon, 13 Mar 2023 10:00:23 +0200 Subject: [PATCH 1/6] wip --- src/YesSql.Abstractions/IQuery.cs | 41 +++++++++--- src/YesSql.Abstractions/QueryExtensions.cs | 8 +++ src/YesSql.Core/Services/DefaultQuery.cs | 73 ++++++++++++++++++---- test/YesSql.Tests/CoreTests.cs | 42 ++++++++++++- 4 files changed, 141 insertions(+), 23 deletions(-) diff --git a/src/YesSql.Abstractions/IQuery.cs b/src/YesSql.Abstractions/IQuery.cs index 5a5edf26..91dfa642 100644 --- a/src/YesSql.Abstractions/IQuery.cs +++ b/src/YesSql.Abstractions/IQuery.cs @@ -21,6 +21,12 @@ public interface IQuery /// The type of index to return IQueryIndex ForIndex() where T : class, IIndex; + /// + /// Defines what type of index should be returned + /// + /// The type of index to return + IQueryIndexAdvanced ForIndexAdvanced() where T : class, IIndex; + /// /// Returns documents from any type /// @@ -63,13 +69,13 @@ public interface IQuery where T : class /// /// The index to filter on. IQuery With() where TIndex : class, IIndex; - + /// /// Filters the documents with a constraint on the specified index. /// /// The index to filter on. IQuery With(Expression> predicate) where TIndex : class, IIndex; - + /// /// Skips the specified number of document. /// @@ -128,7 +134,7 @@ public interface IQueryIndex where T : IIndex /// Joins the document table with an index, and filter it with a predicate. /// IQueryIndex With(Expression> predicate) where TIndex : class, IIndex; - + /// /// Adds a custom Where clause to the query. /// @@ -163,7 +169,7 @@ public interface IQueryIndex where T : IIndex /// Adds an OrderBy clause using a custom lambda expression. /// IQueryIndex ThenBy(Expression> keySelector); - + /// /// Adds a descending OrderBy clause using a custom lambda expression. /// @@ -203,6 +209,23 @@ public interface IQueryIndex where T : IIndex Task CountAsync(); } + /// + /// Represents a query over an index, which can be ordered. + /// + /// The index's type to query over. + public interface IQueryIndexAdvanced : IQueryIndex where T : IIndex + { + /// + /// Filters any predicates on newly joined indexes. + /// + IQueryIndex Any(params Func, IQueryIndex>[] predicates); + + /// + /// Filters all predicates on newly joined indexes. + /// + IQueryIndex All(params Func, IQueryIndex>[] predicates); + } + /// /// Represents a query over an index that targets a specific entity. /// @@ -221,7 +244,7 @@ public interface IQuery : IQuery /// Adds a custom Where clause to the query using a specific dialect. /// IQuery Where(Func sql); - + /// /// Adds a named parameter to the query. /// @@ -236,19 +259,19 @@ public interface IQuery : IQuery /// Sets an OrderBy clause using a custom lambda expression. /// IQuery OrderBy(Expression> keySelector); - + /// /// Sets an OrderBy clause using a custom SQL statement. /// IQuery OrderBy(string sql); - + IQuery OrderByDescending(Expression> keySelector); - + /// /// Sets a descending OrderBy clause using a custom SQL statement. /// IQuery OrderByDescending(string sql); - + /// /// Sets a random OrderBy clause. /// diff --git a/src/YesSql.Abstractions/QueryExtensions.cs b/src/YesSql.Abstractions/QueryExtensions.cs index bcd697b1..9ee8ce43 100644 --- a/src/YesSql.Abstractions/QueryExtensions.cs +++ b/src/YesSql.Abstractions/QueryExtensions.cs @@ -22,6 +22,14 @@ public static IQueryIndex QueryIndex(this ISession session, stri return session.Query(collection).ForIndex(); } + /// + /// Creates a query on an index. + /// + public static IQueryIndexAdvanced QueryIndexAdvanced(this ISession session, string collection = null) where TIndex : class, IIndex + { + return session.Query(collection).ForIndexAdvanced(); + } + /// /// Creates a query on an index, with a predicate. /// diff --git a/src/YesSql.Core/Services/DefaultQuery.cs b/src/YesSql.Core/Services/DefaultQuery.cs index 07b23856..9b51a2fa 100644 --- a/src/YesSql.Core/Services/DefaultQuery.cs +++ b/src/YesSql.Core/Services/DefaultQuery.cs @@ -110,13 +110,13 @@ public QueryState Clone() clone._bindings.Add(binding.Key, new List(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>(_parameterBindings); - + return clone; } } @@ -260,7 +260,7 @@ static DefaultQuery() var objects = Expression.Lambda(expression.Arguments[1]).Compile().DynamicInvoke() as IEnumerable; var values = new List(); - foreach(var o in objects) + foreach (var o in objects) { values.Add(o); } @@ -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 @@ -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); @@ -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; } @@ -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(); @@ -772,7 +772,7 @@ public void ConvertFragment(IStringBuilder builder, Expression expression) var methodCallExpression = (MethodCallExpression)expression; var methodInfo = methodCallExpression.Method; Action action; - if (MethodMappings.TryGetValue(methodInfo, out action) + if (MethodMappings.TryGetValue(methodInfo, out action) || MethodMappings.TryGetValue(methodInfo.GetGenericMethodDefinition(), out action)) { action(this, builder, _dialect, methodCallExpression); @@ -1103,7 +1103,7 @@ IQuery IQuery.For(bool filterType) return new Query(this); } - IQueryIndex IQuery.ForIndex() + IQueryIndex IQuery.ForIndex(bool advanced) { _queryState.GetBindings().Clear(); _queryState.AddBinding(typeof(TIndex)); @@ -1226,7 +1226,7 @@ Task> IQuery.ListAsync() async IAsyncEnumerable IQuery.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; } @@ -1291,7 +1291,7 @@ internal async Task> 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(); @@ -1472,7 +1472,7 @@ private async ValueTask> ComposeQueryAsync(Func, ValueTask(_query); - } + } IQuery IQuery.With() { @@ -1631,6 +1631,53 @@ IQueryIndex IQueryIndex.WithParameter(string name, object value) } } + class QueryIndex : QueryIndex, IQueryIndex where T : class where TIndex : class, IIndex + { + public QueryIndex(DefaultQuery query) : base(query) + { } + + private IQueryIndex ComposeQuery(Func, IQueryIndex>[] predicates, CompositeNode predicate) + { + _query._queryState._currentPredicate.Children.Add(predicate); + + _query._queryState._currentPredicate = predicate; + + foreach (var p in predicates) + { + var name = "a" + (_query._queryState._bindings.Count + 1); + _query._queryState._bindingName = name; + _query._queryState._bindings.Add(name, new List()); + + p(this); + } + + return new QueryIndex(_query); + } + + IQueryIndex IQueryIndex.Any(params Func, IQueryIndex>[] predicates) + { + // Scope the currentPredicate so multiple calls will not act on the new predicate. + var currentPredicate = _query._queryState._currentPredicate; + var query = ComposeQuery(predicates, new OrNode()); + // Return the currentPredicate to it's previous value, so another method call will act on the previous predicate. + _query._queryState._currentPredicate = currentPredicate; + + return query; + } + + IQueryIndex IQueryIndex.All(params Func, IQueryIndex>[] predicates) + { + // Scope the currentPredicate so multiple calls will not act on the new predicate. + var currentPredicate = _query._queryState._currentPredicate; + var query = ComposeQuery(predicates, new AndNode()); + // Return the currentPredicate to it's previous value, so another method call will act on the previous predicate. + _query._queryState._currentPredicate = currentPredicate; + + return query; + } + + } + class Query : Query, IQuery where T : class where TIndex : IIndex diff --git a/test/YesSql.Tests/CoreTests.cs b/test/YesSql.Tests/CoreTests.cs index e5ea6663..cdbd9f82 100644 --- a/test/YesSql.Tests/CoreTests.cs +++ b/test/YesSql.Tests/CoreTests.cs @@ -45,7 +45,7 @@ public async Task InitializeAsync() if (_configuration == null) { _configuration = CreateConfiguration(); - + CleanDatabase(_configuration, false); _store = await StoreFactory.CreateAndInitializeAsync(_configuration); @@ -1583,6 +1583,46 @@ public async Task ShouldCreateSeveralMapIndexPerDocument() } } + [Fact] + public async Task ShouldQueryIndexMultipleIndexes() + { + // We should be able to query documents on multiple rows in an index + // This mean the same Index table needs to be JOINed + + _store.RegisterIndexes(); + + using (var session = _store.CreateSession()) + { + var hanselman = new Person + { + Firstname = "Scott", + Lastname = "Hanselman" + }; + + 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.QueryIndexAdvanced() + .Any( + x => x.With(x => x.Identity == "Hanselman"), + x => x.With(x => x.Identity == "Guthrie")) + .CountAsync() + ); + } + } + + [Fact] public async Task ShouldQueryMultipleIndexes() { From 31cbe61bf214d42e09dc27a1745f88446243fe3c Mon Sep 17 00:00:00 2001 From: Michael Petrinolis Date: Mon, 13 Mar 2023 10:01:17 +0200 Subject: [PATCH 2/6] wip --- src/YesSql.Core/Services/DefaultQuery.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/YesSql.Core/Services/DefaultQuery.cs b/src/YesSql.Core/Services/DefaultQuery.cs index 9b51a2fa..b1c026e7 100644 --- a/src/YesSql.Core/Services/DefaultQuery.cs +++ b/src/YesSql.Core/Services/DefaultQuery.cs @@ -1103,7 +1103,7 @@ IQuery IQuery.For(bool filterType) return new Query(this); } - IQueryIndex IQuery.ForIndex(bool advanced) + IQueryIndex IQuery.ForIndex() { _queryState.GetBindings().Clear(); _queryState.AddBinding(typeof(TIndex)); From 92dd5b3399a710d149529d59d997e7ba44d6e6dc Mon Sep 17 00:00:00 2001 From: mpetrinolis Date: Mon, 13 Mar 2023 18:03:15 +0200 Subject: [PATCH 3/6] support AdvancedQueryIndex --- src/YesSql.Abstractions/IQuery.cs | 19 +----- src/YesSql.Abstractions/QueryExtensions.cs | 4 +- src/YesSql.Core/Services/DefaultQuery.cs | 75 ++++++++-------------- test/YesSql.Tests/CoreTests.cs | 19 ++++-- 4 files changed, 43 insertions(+), 74 deletions(-) diff --git a/src/YesSql.Abstractions/IQuery.cs b/src/YesSql.Abstractions/IQuery.cs index 91dfa642..32550285 100644 --- a/src/YesSql.Abstractions/IQuery.cs +++ b/src/YesSql.Abstractions/IQuery.cs @@ -25,7 +25,7 @@ public interface IQuery /// Defines what type of index should be returned /// /// The type of index to return - IQueryIndexAdvanced ForIndexAdvanced() where T : class, IIndex; + IQuery ForAdvancedIndex() where T : class, IIndex; /// /// Returns documents from any type @@ -209,23 +209,6 @@ public interface IQueryIndex where T : IIndex Task CountAsync(); } - /// - /// Represents a query over an index, which can be ordered. - /// - /// The index's type to query over. - public interface IQueryIndexAdvanced : IQueryIndex where T : IIndex - { - /// - /// Filters any predicates on newly joined indexes. - /// - IQueryIndex Any(params Func, IQueryIndex>[] predicates); - - /// - /// Filters all predicates on newly joined indexes. - /// - IQueryIndex All(params Func, IQueryIndex>[] predicates); - } - /// /// Represents a query over an index that targets a specific entity. /// diff --git a/src/YesSql.Abstractions/QueryExtensions.cs b/src/YesSql.Abstractions/QueryExtensions.cs index 9ee8ce43..28a126b8 100644 --- a/src/YesSql.Abstractions/QueryExtensions.cs +++ b/src/YesSql.Abstractions/QueryExtensions.cs @@ -25,9 +25,9 @@ public static IQueryIndex QueryIndex(this ISession session, stri /// /// Creates a query on an index. /// - public static IQueryIndexAdvanced QueryIndexAdvanced(this ISession session, string collection = null) where TIndex : class, IIndex + public static IQuery AdvancedQueryIndex(this ISession session, string collection = null) where TIndex : class, IIndex { - return session.Query(collection).ForIndexAdvanced(); + return session.Query(collection).ForAdvancedIndex(); } /// diff --git a/src/YesSql.Core/Services/DefaultQuery.cs b/src/YesSql.Core/Services/DefaultQuery.cs index b1c026e7..50f8f38e 100644 --- a/src/YesSql.Core/Services/DefaultQuery.cs +++ b/src/YesSql.Core/Services/DefaultQuery.cs @@ -1113,6 +1113,34 @@ IQueryIndex IQuery.ForIndex() return new QueryIndex(this); } + IQuery IQuery.ForAdvancedIndex() + { + _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(this); + } + IQuery IQuery.Any() { _queryState.GetBindings().Clear(); @@ -1631,53 +1659,6 @@ IQueryIndex IQueryIndex.WithParameter(string name, object value) } } - class QueryIndex : QueryIndex, IQueryIndex where T : class where TIndex : class, IIndex - { - public QueryIndex(DefaultQuery query) : base(query) - { } - - private IQueryIndex ComposeQuery(Func, IQueryIndex>[] predicates, CompositeNode predicate) - { - _query._queryState._currentPredicate.Children.Add(predicate); - - _query._queryState._currentPredicate = predicate; - - foreach (var p in predicates) - { - var name = "a" + (_query._queryState._bindings.Count + 1); - _query._queryState._bindingName = name; - _query._queryState._bindings.Add(name, new List()); - - p(this); - } - - return new QueryIndex(_query); - } - - IQueryIndex IQueryIndex.Any(params Func, IQueryIndex>[] predicates) - { - // Scope the currentPredicate so multiple calls will not act on the new predicate. - var currentPredicate = _query._queryState._currentPredicate; - var query = ComposeQuery(predicates, new OrNode()); - // Return the currentPredicate to it's previous value, so another method call will act on the previous predicate. - _query._queryState._currentPredicate = currentPredicate; - - return query; - } - - IQueryIndex IQueryIndex.All(params Func, IQueryIndex>[] predicates) - { - // Scope the currentPredicate so multiple calls will not act on the new predicate. - var currentPredicate = _query._queryState._currentPredicate; - var query = ComposeQuery(predicates, new AndNode()); - // Return the currentPredicate to it's previous value, so another method call will act on the previous predicate. - _query._queryState._currentPredicate = currentPredicate; - - return query; - } - - } - class Query : Query, IQuery where T : class where TIndex : IIndex diff --git a/test/YesSql.Tests/CoreTests.cs b/test/YesSql.Tests/CoreTests.cs index cdbd9f82..b56d4268 100644 --- a/test/YesSql.Tests/CoreTests.cs +++ b/test/YesSql.Tests/CoreTests.cs @@ -1584,19 +1584,21 @@ public async Task ShouldCreateSeveralMapIndexPerDocument() } [Fact] - public async Task ShouldQueryIndexMultipleIndexes() + public async Task ShouldAdvancedQueryMultipleIndexes() { - // We should be able to query documents on multiple rows in an index + // 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(); + _store.RegisterIndexes(); using (var session = _store.CreateSession()) { var hanselman = new Person { Firstname = "Scott", - Lastname = "Hanselman" + Lastname = "Hanselman", + Age = -1 }; var guthrie = new Person @@ -1613,10 +1615,13 @@ public async Task ShouldQueryIndexMultipleIndexes() using (var session = _store.CreateSession()) { - Assert.Equal(2, await session.QueryIndexAdvanced() - .Any( - x => x.With(x => x.Identity == "Hanselman"), - x => x.With(x => x.Identity == "Guthrie")) + Assert.Equal(2, await session.AdvancedQueryIndex() + .All( + x => x.With(x => x.Age == null || x.Age == 0), + x => x.Any( + x => x.With(x => x.Identity == "Hanselman"), + x => x.With(x => x.Identity == "Guthrie")) + ) .CountAsync() ); } From d824ff21e133d7d2fe41d7369affed8bd353bba7 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Fri, 15 Nov 2024 08:27:25 -0800 Subject: [PATCH 4/6] Rewrite tests without new method --- test/YesSql.Tests/CoreTests.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/YesSql.Tests/CoreTests.cs b/test/YesSql.Tests/CoreTests.cs index 4e5f68b6..459b1162 100644 --- a/test/YesSql.Tests/CoreTests.cs +++ b/test/YesSql.Tests/CoreTests.cs @@ -1680,7 +1680,17 @@ public async Task ShouldAdvancedQueryMultipleIndexes() using (var session = _store.CreateSession()) { - Assert.Equal(2, await session.AdvancedQueryIndex() + // Assert.Equal(2, await session.AdvancedQueryIndex() + // .All( + // x => x.With(x => x.Age == null || x.Age == 0), + // x => x.Any( + // x => x.With(x => x.Identity == "Hanselman"), + // x => x.With(x => x.Identity == "Guthrie")) + // ) + // .CountAsync() + // ); + + Assert.Equal(2, await session.Query() .All( x => x.With(x => x.Age == null || x.Age == 0), x => x.Any( From d69f6574a8c1a67f99337a0341f8ea7cb7629cd7 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Fri, 15 Nov 2024 08:45:07 -0800 Subject: [PATCH 5/6] Fix args order --- src/YesSql.Abstractions/IQuery.cs | 2 +- src/YesSql.Abstractions/QueryExtensions.cs | 6 +++--- src/YesSql.Core/Services/DefaultQuery.cs | 4 ++-- test/YesSql.Tests/CoreTests.cs | 18 ++++-------------- 4 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/YesSql.Abstractions/IQuery.cs b/src/YesSql.Abstractions/IQuery.cs index 32550285..5bc28917 100644 --- a/src/YesSql.Abstractions/IQuery.cs +++ b/src/YesSql.Abstractions/IQuery.cs @@ -25,7 +25,7 @@ public interface IQuery /// Defines what type of index should be returned /// /// The type of index to return - IQuery ForAdvancedIndex() where T : class, IIndex; + IQuery ForIndexJoined() where T : class, IIndex; /// /// Returns documents from any type diff --git a/src/YesSql.Abstractions/QueryExtensions.cs b/src/YesSql.Abstractions/QueryExtensions.cs index 28a126b8..38b20a11 100644 --- a/src/YesSql.Abstractions/QueryExtensions.cs +++ b/src/YesSql.Abstractions/QueryExtensions.cs @@ -23,11 +23,11 @@ public static IQueryIndex QueryIndex(this ISession session, stri } /// - /// Creates a query on an index. + /// Creates a query on an index and other joined indexes. /// - public static IQuery AdvancedQueryIndex(this ISession session, string collection = null) where TIndex : class, IIndex + public static IQuery QueryIndexJoined(this ISession session, string collection = null) where TIndex : class, IIndex { - return session.Query(collection).ForAdvancedIndex(); + return session.Query(collection).ForIndexJoined(); } /// diff --git a/src/YesSql.Core/Services/DefaultQuery.cs b/src/YesSql.Core/Services/DefaultQuery.cs index 84caa14e..d7b9c422 100644 --- a/src/YesSql.Core/Services/DefaultQuery.cs +++ b/src/YesSql.Core/Services/DefaultQuery.cs @@ -1196,7 +1196,7 @@ IQueryIndex IQuery.ForIndex() return new QueryIndex(this); } - IQuery IQuery.ForAdvancedIndex() + IQuery IQuery.ForIndexJoined() { _queryState.GetBindings().Clear(); @@ -1214,7 +1214,7 @@ IQuery IQuery.ForAdvancedIndex() if (typeof(MapIndex).IsAssignableFrom(tIndex)) { - _queryState._sqlBuilder.InnerJoin(_queryState._documentTable, indexTableAlias, "DocumentId", _queryState._documentTable, _queryState._store.Configuration.Schema, "Id"); + _queryState._sqlBuilder.InnerJoin(_queryState._documentTable, indexTableAlias, "DocumentId", _queryState._documentTable, "Id", _queryState._store.Configuration.Schema); } else { diff --git a/test/YesSql.Tests/CoreTests.cs b/test/YesSql.Tests/CoreTests.cs index 459b1162..b6ed44cb 100644 --- a/test/YesSql.Tests/CoreTests.cs +++ b/test/YesSql.Tests/CoreTests.cs @@ -1649,7 +1649,7 @@ public async Task ShouldCreateSeveralMapIndexPerDocument() } [Fact] - public async Task ShouldAdvancedQueryMultipleIndexes() + public async Task ShouldQueryJoinedIndexes() { // We should be able to query documents on multiple rows in multiple index // This mean the same Index table needs to be JOINed @@ -1672,25 +1672,15 @@ public async Task ShouldAdvancedQueryMultipleIndexes() Lastname = "Guthrie" }; - session.Save(hanselman); - session.Save(guthrie); + await session.SaveAsync(hanselman); + await session.SaveAsync(guthrie); await session.SaveChangesAsync(); } using (var session = _store.CreateSession()) { - // Assert.Equal(2, await session.AdvancedQueryIndex() - // .All( - // x => x.With(x => x.Age == null || x.Age == 0), - // x => x.Any( - // x => x.With(x => x.Identity == "Hanselman"), - // x => x.With(x => x.Identity == "Guthrie")) - // ) - // .CountAsync() - // ); - - Assert.Equal(2, await session.Query() + Assert.Equal(2, await session.QueryIndexJoined() .All( x => x.With(x => x.Age == null || x.Age == 0), x => x.Any( From 90978f711df537a1923f2d0c5d43254d3d713d65 Mon Sep 17 00:00:00 2001 From: Michael Petrinolis Date: Sun, 24 Nov 2024 21:37:19 +0200 Subject: [PATCH 6/6] Add onTableIsAlias param to Join methods and update tests Updated ISqlBuilder and SqlBuilder Join methods to include a new onTableIsAlias parameter. Modified InnerJoin, LeftJoin, and RightJoin extension methods to pass this parameter. Adjusted DefaultQuery InnerJoin call to set onTableIsAlias to true for _documentTable. Added a new test ShouldReturnFirstOrDefaultJoinedIndexes in CoreTests and made minor formatting changes. --- src/YesSql.Abstractions/ISqlBuilder.cs | 2 +- .../SqlBuilderExtensions.cs | 12 ++--- src/YesSql.Core/Services/DefaultQuery.cs | 2 +- src/YesSql.Core/Sql/SqlBuilder.cs | 6 +-- test/YesSql.Tests/CoreTests.cs | 47 ++++++++++++++++++- 5 files changed, 57 insertions(+), 12 deletions(-) diff --git a/src/YesSql.Abstractions/ISqlBuilder.cs b/src/YesSql.Abstractions/ISqlBuilder.cs index abad9a0c..8b7df81f 100644 --- a/src/YesSql.Abstractions/ISqlBuilder.cs +++ b/src/YesSql.Abstractions/ISqlBuilder.cs @@ -43,7 +43,7 @@ public interface ISqlBuilder ISqlBuilder Clone(); IEnumerable GetSelectors(); IEnumerable GetOrders(); - void Join(JoinType type, string table, string onTable, string onColumn, string toTable, string toColumn, string schema, string alias = null, string toAlias = null); + void Join(JoinType type, string table, string onTable, string onColumn, string toTable, string toColumn, string schema, string alias = null, string toAlias = null, bool onTableIsAlias = false); void HavingAnd(string having); void HavingOr(string having); } diff --git a/src/YesSql.Abstractions/SqlBuilderExtensions.cs b/src/YesSql.Abstractions/SqlBuilderExtensions.cs index 1fdff115..41909976 100644 --- a/src/YesSql.Abstractions/SqlBuilderExtensions.cs +++ b/src/YesSql.Abstractions/SqlBuilderExtensions.cs @@ -2,12 +2,12 @@ namespace YesSql; public static class SqlBuilderExtensions { - public static void InnerJoin(this ISqlBuilder builder, string table, string onTable, string onColumn, string toTable, string toColumn, string schema, string alias = null, string toAlias = null) - => builder.Join(JoinType.Inner, table, onTable, onColumn, toTable, toColumn, schema, alias, toAlias); + public static void InnerJoin(this ISqlBuilder builder, string table, string onTable, string onColumn, string toTable, string toColumn, string schema, string alias = null, string toAlias = null, bool onTableIsAlias = false) + => builder.Join(JoinType.Inner, table, onTable, onColumn, toTable, toColumn, schema, alias, toAlias, onTableIsAlias); - public static void LeftJoin(this ISqlBuilder builder, string table, string onTable, string onColumn, string toTable, string toColumn, string schema, string alias = null, string toAlias = null) - => builder.Join(JoinType.Left, table, onTable, onColumn, toTable, toColumn, schema, alias, toAlias); + public static void LeftJoin(this ISqlBuilder builder, string table, string onTable, string onColumn, string toTable, string toColumn, string schema, string alias = null, string toAlias = null, bool onTableIsAlias = false) + => builder.Join(JoinType.Left, table, onTable, onColumn, toTable, toColumn, schema, alias, toAlias, onTableIsAlias); - public static void RightJoin(this ISqlBuilder builder, string table, string onTable, string onColumn, string toTable, string toColumn, string schema, string alias = null, string toAlias = null) - => builder.Join(JoinType.Right, table, onTable, onColumn, toTable, toColumn, schema, alias, toAlias); + public static void RightJoin(this ISqlBuilder builder, string table, string onTable, string onColumn, string toTable, string toColumn, string schema, string alias = null, string toAlias = null, bool onTableIsAlias = false) + => builder.Join(JoinType.Right, table, onTable, onColumn, toTable, toColumn, schema, alias, toAlias, onTableIsAlias); } diff --git a/src/YesSql.Core/Services/DefaultQuery.cs b/src/YesSql.Core/Services/DefaultQuery.cs index d7b9c422..ccce9796 100644 --- a/src/YesSql.Core/Services/DefaultQuery.cs +++ b/src/YesSql.Core/Services/DefaultQuery.cs @@ -1214,7 +1214,7 @@ IQuery IQuery.ForIndexJoined() if (typeof(MapIndex).IsAssignableFrom(tIndex)) { - _queryState._sqlBuilder.InnerJoin(_queryState._documentTable, indexTableAlias, "DocumentId", _queryState._documentTable, "Id", _queryState._store.Configuration.Schema); + _queryState._sqlBuilder.InnerJoin(_queryState._documentTable, indexTableAlias, "DocumentId", _queryState._documentTable, "Id", _queryState._store.Configuration.Schema, onTableIsAlias: true); } else { diff --git a/src/YesSql.Core/Sql/SqlBuilder.cs b/src/YesSql.Core/Sql/SqlBuilder.cs index 321670f6..22d059d2 100644 --- a/src/YesSql.Core/Sql/SqlBuilder.cs +++ b/src/YesSql.Core/Sql/SqlBuilder.cs @@ -71,10 +71,10 @@ public void Skip(string skip) public void Take(string take) => _count = take; - public virtual void Join(JoinType type, string table, string onTable, string onColumn, string toTable, string toColumn, string schema, string alias = null, string toAlias = null) + public virtual void Join(JoinType type, string table, string onTable, string onColumn, string toTable, string toColumn, string schema, string alias = null, string toAlias = null, bool onTableIsAlias = false) { // Don't prefix if alias is used - if (alias != onTable) + if (alias != onTable && !onTableIsAlias) { onTable = _dialect.QuoteForTableName(onTable, schema); } @@ -92,7 +92,7 @@ public virtual void Join(JoinType type, string table, string onTable, string onC toTable = _tablePrefix + toTable; } - if (!string.IsNullOrEmpty(toAlias)) + if (!string.IsNullOrEmpty(toAlias) && !onTableIsAlias) { toTable = _dialect.QuoteForAliasName(toAlias); } diff --git a/test/YesSql.Tests/CoreTests.cs b/test/YesSql.Tests/CoreTests.cs index b6ed44cb..d1ef946d 100644 --- a/test/YesSql.Tests/CoreTests.cs +++ b/test/YesSql.Tests/CoreTests.cs @@ -980,7 +980,7 @@ public async Task DetachAllRemoveAllEntitiesFromIdentityMap() Assert.NotEqual(p3, p3a); // The identity should be valid as we do only reads - + var p1b = await session.GetAsync(p1.Id); var p2b = await session.GetAsync(p2.Id); var p3b = await session.GetAsync(p3.Id); @@ -1692,6 +1692,51 @@ public async Task ShouldQueryJoinedIndexes() } } + [Fact] + public async Task ShouldReturnFirstOrDefaultJoinedIndexes() + { + _store.RegisterIndexes(); + _store.RegisterIndexes(); + + await using (var session = _store.CreateSession()) + { + var bill = new Person { Firstname = "Bill", Lastname = "Gates" }; + var scotth = new Person { Firstname = "Scott", Lastname = "Hanselman" }; + var scottg = new Person { Firstname = "Scott", Lastname = "Guthrie" }; + var scotts = new Person { Firstname = "Scott", Lastname = "Smith", Age = 33 }; + var clint = new Person { Firstname = "Clint", Lastname = "Eastwood", Age = 110 }; + var smith = new Person { Firstname = "John", Lastname = "Smith", Age = 20 }; + + await session.SaveAsync(bill); + await session.SaveAsync(scotth); + await session.SaveAsync(scottg); + await session.SaveAsync(scotts); + await session.SaveAsync(smith); + await session.SaveAsync(clint); + + await session.SaveChangesAsync(); + } + + await using (var session = _store.CreateSession()) + { + var qry = session.QueryIndexJoined() + .Any(d => d.All( + x => x.With(d => d.SomeName == "Clint" || d.SomeName.StartsWith("S")), + y => y.With(d => d.Adult == true) + ), d => d.All( + x => x.With(d => d.SomeName == "Scott"), + y => y.With(d => d.Adult == false)) + ); + + var persons = await qry.ListAsync(); + Assert.Collection(persons, + x => Assert.Equal(110, x.Age), + x => Assert.Equal(0, x.Age), + x => Assert.Equal(0, x.Age) + ); + } + } + [Fact] public async Task ShouldQueryMultipleIndexes()