diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs index 6404fc183..c7ac191b4 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs @@ -46,11 +46,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets { new CreateIndexModel( Index + .Descending(x => x.LastModified) + .Ascending(x => x.Id) .Ascending(x => x.IndexedAppId) .Ascending(x => x.IsDeleted) .Ascending(x => x.ParentId) - .Ascending(x => x.Tags) - .Descending(x => x.LastModified)), + .Ascending(x => x.Tags)), new CreateIndexModel( Index .Ascending(x => x.IndexedAppId) @@ -99,7 +100,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets var filter = BuildFilter(appId, q.Ids.ToHashSet()); var assetEntities = - await Collection.Find(filter).SortByDescending(x => x.LastModified) + await Collection.Find(filter).SortByDescending(x => x.LastModified).ThenBy(x => x.Id) .QueryLimit(q.Query) .QuerySkip(q.Query) .ToListAsync(ct = default); diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs index 1bde3b56b..cf40963ed 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs @@ -42,6 +42,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors { var filters = new List> { + Filter.Exists(x => x.LastModified), + Filter.Exists(x => x.Id), Filter.Eq(x => x.IndexedAppId, appId) }; diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs index 7ef366378..0ddab3177 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs @@ -45,11 +45,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations { var indexBySchemaWithRefs = new CreateIndexModel(Index + .Descending(x => x.LastModified) + .Ascending(x => x.Id) .Ascending(x => x.IndexedAppId) .Ascending(x => x.IndexedSchemaId) .Ascending(x => x.IsDeleted) - .Ascending(x => x.ReferencedIds) - .Descending(x => x.LastModified)); + .Ascending(x => x.ReferencedIds)); await Collection.Indexes.CreateOneAsync(indexBySchemaWithRefs, cancellationToken: ct); @@ -202,13 +203,20 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations private static bool IsSatisfiedByIndex(ClrQuery query) { - return query.Sort?.All(x => x.Path.ToString() == "mt" && x.Order == SortOrder.Descending) == true; + return query.Sort != null && + query.Sort.Count == 2 && + query.Sort[0].Path.ToString() == "mt" && + query.Sort[0].Order == SortOrder.Descending && + query.Sort[1].Path.ToString() == "id" && + query.Sort[1].Order == SortOrder.Ascending; } private static FilterDefinition BuildFilter(DomainId appId, DomainId schemaId, FilterNode? filter) { var filters = new List> { + Filter.Exists(x => x.LastModified), + Filter.Exists(x => x.Id), Filter.Eq(x => x.IndexedAppId, appId), Filter.Eq(x => x.IndexedSchemaId, schemaId) }; @@ -231,6 +239,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations { var filters = new List> { + Filter.Exists(x => x.LastModified), + Filter.Exists(x => x.Id), Filter.Eq(x => x.IndexedAppId, appId), Filter.In(x => x.IndexedSchemaId, schemaIds), }; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryFixture.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryFixture.cs index c1366c730..71a676347 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryFixture.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryFixture.cs @@ -87,6 +87,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb { DocumentId = DomainId.NewGuid(), Tags = new HashSet { tag }, + Id = DomainId.NewGuid(), FileHash = fileName, FileName = fileName, FileSize = 1024, diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryIntegrationTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryIntegrationTests.cs index 5d549962f..a41a53f0b 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryIntegrationTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/AssetsQueryIntegrationTests.cs @@ -138,18 +138,22 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb yield return new object?[] { DomainId.Empty }; } - private async Task> QueryAsync(DomainId? parentId, ClrQuery query) + private async Task> QueryAsync(DomainId? parentId, ClrQuery clrQuery) { - query.Top = 1000; + clrQuery.Top = 1000; - query.Skip = 100; + clrQuery.Skip = 100; - query.Sort = new List + if (clrQuery.Sort.Count == 0) { - new SortNode("LastModified", SortOrder.Descending) - }; - - var assets = await _.AssetRepository.QueryAsync(_.RandomAppId(), parentId, Q.Empty.WithQuery(query)); + clrQuery.Sort = new List + { + new SortNode("LastModified", SortOrder.Descending), + new SortNode("Id", SortOrder.Descending) + }; + } + + var assets = await _.AssetRepository.QueryAsync(_.RandomAppId(), parentId, Q.Empty.WithQuery(clrQuery)); return assets; } diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryIntegrationTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryIntegrationTests.cs index 71964b08a..1e6bcabcb 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryIntegrationTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/ContentsQueryIntegrationTests.cs @@ -30,25 +30,18 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb _ = fixture; } - public IEnumerable Collections() - { - yield return new[] { _.ContentRepository }; - } - - [Theory] - [MemberData(nameof(Collections))] - public async Task Should_verify_ids(IContentRepository repository) + [Fact] + public async Task Should_verify_ids() { var ids = Enumerable.Repeat(0, 50).Select(_ => DomainId.NewGuid()).ToHashSet(); - var contents = await repository.QueryIdsAsync(_.RandomAppId(), ids, SearchScope.Published); + var contents = await _.ContentRepository.QueryIdsAsync(_.RandomAppId(), ids, SearchScope.Published); Assert.NotNull(contents); } - [Theory] - [MemberData(nameof(Collections))] - public async Task Should_query_contents_by_ids(IContentRepository repository) + [Fact] + public async Task Should_query_contents_by_ids() { var ids = Enumerable.Repeat(0, 50).Select(_ => DomainId.NewGuid()).ToHashSet(); @@ -57,85 +50,74 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb _.RandomSchema() }; - var contents = await repository.QueryAsync(_.RandomApp(), schemas, Q.Empty.WithIds(ids), SearchScope.All); + var contents = await _.ContentRepository.QueryAsync(_.RandomApp(), schemas, Q.Empty.WithIds(ids), SearchScope.All); Assert.NotNull(contents); } - [Theory] - [MemberData(nameof(Collections))] - public async Task Should_query_contents_by_ids_and_schema(IContentRepository repository) + [Fact] + public async Task Should_query_contents_by_ids_and_schema() { var ids = Enumerable.Repeat(0, 50).Select(_ => DomainId.NewGuid()).ToHashSet(); - var contents = await repository.QueryAsync(_.RandomApp(), _.RandomSchema(), Q.Empty.WithIds(ids), SearchScope.All); + var contents = await _.ContentRepository.QueryAsync(_.RandomApp(), _.RandomSchema(), Q.Empty.WithIds(ids), SearchScope.All); Assert.NotNull(contents); } - [Theory] - [MemberData(nameof(Collections))] - public async Task Should_query_contents_ids_by_filter(IContentRepository repository) + [Fact] + public async Task Should_query_contents_ids_by_filter() { - var filter = F.Eq("data.value.iv", 12); + var filter = F.Eq("data.field1.iv", 12); - var contents = await repository.QueryIdsAsync(_.RandomAppId(), _.RandomSchemaId(), filter); + var contents = await _.ContentRepository.QueryIdsAsync(_.RandomAppId(), _.RandomSchemaId(), filter); Assert.NotEmpty(contents); } - [Theory] - [MemberData(nameof(Collections))] - public async Task Should_query_contents_by_filter(IContentRepository repository) + [Fact] + public async Task Should_query_contents_by_filter() { var query = new ClrQuery { - Sort = new List - { - new SortNode("lastModified", SortOrder.Descending) - }, - Filter = F.Eq("data.value.iv", 12) + Filter = F.Eq("data.field1.iv", 12) }; - var contents = await repository.QueryAsync(_.RandomApp(), _.RandomSchema(), Q.Empty.WithQuery(query), SearchScope.Published); + var contents = await QueryAsync(_.ContentRepository, query, 1000, 0); Assert.NotEmpty(contents); } - [Theory] - [MemberData(nameof(Collections))] - public async Task Should_query_contents_scheduled(IContentRepository repository) + [Fact] + public async Task Should_query_contents_scheduled() { var time = SystemClock.Instance.GetCurrentInstant(); - await repository.QueryScheduledWithoutDataAsync(time, _ => Task.CompletedTask); + await _.ContentRepository.QueryScheduledWithoutDataAsync(time, _ => Task.CompletedTask); } - [Theory] - [MemberData(nameof(Collections))] - public async Task Should_query_contents_with_default_query(IContentRepository repository) + [Fact] + public async Task Should_query_contents_with_default_query() { var query = new ClrQuery(); - var contents = await QueryAsync(repository, query); + var contents = await QueryAsync(_.ContentRepository, query); Assert.NotEmpty(contents); } - [Theory] - [MemberData(nameof(Collections))] - public async Task Should_query_contents_with_default_query_and_id(IContentRepository repository) + [Fact] + public async Task Should_query_contents_with_default_query_and_id() { var query = new ClrQuery(); - var contents = await QueryAsync(repository, query, reference: DomainId.NewGuid()); + var contents = await QueryAsync(_.ContentRepository, query, reference: DomainId.NewGuid()); Assert.Empty(contents); } - [Theory] - [MemberData(nameof(Collections))] - public async Task Should_query_contents_with_large_skip(IContentRepository repository) + [Fact] + public async Task Should_query_contents_with_large_skip() { var query = new ClrQuery { @@ -145,54 +127,51 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb } }; - var contents = await QueryAsync(repository, query, 1000, 9000); + var contents = await QueryAsync(_.ContentRepository, query, 1000, 9000); Assert.NotEmpty(contents); } - [Theory] - [MemberData(nameof(Collections))] - public async Task Should_query_contents_with_query_fulltext(IContentRepository repository) + [Fact] + public async Task Should_query_contents_with_query_fulltext() { var query = new ClrQuery { FullText = "hello" }; - var contents = await QueryAsync(repository, query); + var contents = await QueryAsync(_.ContentRepository, query); Assert.NotNull(contents); } - [Theory] - [MemberData(nameof(Collections))] - public async Task Should_query_contents_with_query_filter(IContentRepository repository) + [Fact] + public async Task Should_query_contents_with_query_filter() { var query = new ClrQuery { - Filter = F.Eq("data.value.iv", 200) + Filter = F.Eq("data.field1.iv", 200) }; - var contents = await QueryAsync(repository, query, 1000, 0); + var contents = await QueryAsync(_.ContentRepository, query, 1000, 0); Assert.NotEmpty(contents); } - [Theory] - [MemberData(nameof(Collections))] - public async Task Should_query_contents_with_query_filter_and_id(IContentRepository repository) + [Fact] + public async Task Should_query_contents_with_query_filter_and_id() { var query = new ClrQuery { Filter = F.Eq("data.value.iv", 12) }; - var contents = await QueryAsync(repository, query, 1000, 0, reference: DomainId.NewGuid()); + var contents = await QueryAsync(_.ContentRepository, query, 1000, 0, reference: DomainId.NewGuid()); Assert.Empty(contents); } - private async Task> QueryAsync(IContentRepository repository, + private async Task> QueryAsync(IContentRepository contentRepository, ClrQuery clrQuery, int take = 1000, int skip = 100, @@ -212,7 +191,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb { clrQuery.Sort = new List { - new SortNode("LastModified", SortOrder.Descending) + new SortNode("LastModified", SortOrder.Descending), + new SortNode("Id", SortOrder.Ascending) }; } @@ -221,7 +201,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb .WithQuery(clrQuery) .WithReference(reference); - var contents = await repository.QueryAsync(_.RandomApp(), _.RandomSchema(), q, SearchScope.All); + var contents = await contentRepository.QueryAsync(_.RandomApp(), _.RandomSchema(), q, SearchScope.All); return contents; }