Browse Source

Some progress.

pull/349/head
Sebastian Stehle 7 years ago
parent
commit
6935426784
  1. 123
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs
  2. 153
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs
  3. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs
  4. 67
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs
  5. 54
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  6. 8
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_EventHandling.cs
  7. 14
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs
  8. 44
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs
  9. 2
      src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs
  10. 76
      src/Squidex.Domain.Apps.Entities/Contents/Text/GrainTextIndexer.cs
  11. 25
      src/Squidex.Domain.Apps.Entities/Contents/Text/ITextIndexer.cs
  12. 24
      src/Squidex.Domain.Apps.Entities/Contents/Text/ITextIndexerGrain.cs
  13. 18
      src/Squidex.Domain.Apps.Entities/Contents/Text/IndexData.cs
  14. 23
      src/Squidex.Domain.Apps.Entities/Contents/Text/SearchContext.cs
  15. 47
      src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexer.cs
  16. 187
      src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs
  17. 4
      src/Squidex/Config/Domain/EntitiesServices.cs
  18. 44
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTests.cs

123
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs

@ -11,50 +11,57 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MongoDB.Driver; using MongoDB.Driver;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors; using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb; using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
internal class MongoContentCollection : MongoRepositoryBase<MongoContentEntity> internal class MongoContentCollection : MongoRepositoryBase<MongoContentEntity>
{ {
private readonly string collectionName;
protected IJsonSerializer Serializer { get; } protected IJsonSerializer Serializer { get; }
public MongoContentCollection(IMongoDatabase database, IJsonSerializer serializer, string collectionName) public MongoContentCollection(IMongoDatabase database, IJsonSerializer serializer)
: base(database) : base(database)
{ {
this.collectionName = collectionName;
Serializer = serializer; Serializer = serializer;
} }
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default) protected override Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default)
{ {
await collection.Indexes.CreateOneAsync( return collection.Indexes.CreateManyAsync(new[]
new CreateIndexModel<MongoContentEntity>(Index.Ascending(x => x.ReferencedIds)), cancellationToken: ct); {
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedSchemaId).Ascending(x => x.Id).Ascending(x => x.Status)),
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.ScheduledAt).Ascending(x => x.IsDeleted)),
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.ReferencedIds))
}, ct);
} }
protected override string CollectionName() protected override string CollectionName()
{ {
return collectionName; return "State_Contents";
} }
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Query query, Status[] status = null, bool useDraft = false) public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Query query, List<Guid> ids, Status[] status = null, bool useDraft = false)
{ {
try try
{ {
query = query.AdjustToModel(schema.SchemaDef, useDraft); query = query.AdjustToModel(schema.SchemaDef, useDraft);
var filter = query.ToFilter(schema.Id, status); var filter = query.ToFilter(schema.Id, ids, status);
var contentCount = Collection.Find(filter).CountDocumentsAsync(); var contentCount = Collection.Find(filter).CountDocumentsAsync();
var contentItems = var contentItems =
@ -62,7 +69,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
.ContentTake(query) .ContentTake(query)
.ContentSkip(query) .ContentSkip(query)
.ContentSort(query) .ContentSort(query)
.Not(x => x.DataText)
.ToListAsync(); .ToListAsync();
await Task.WhenAll(contentItems, contentCount); await Task.WhenAll(contentItems, contentCount);
@ -89,12 +95,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, HashSet<Guid> ids, Status[] status = null) public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, HashSet<Guid> ids, Status[] status = null)
{ {
var find = var find = Collection.Find(FilterFactory.Build(schema.Id, ids, status));
status != null && status.Length > 0 ?
Collection.Find(x => x.IndexedSchemaId == schema.Id && ids.Contains(x.Id) && x.IsDeleted != true && status.Contains(x.Status)) :
Collection.Find(x => x.IndexedSchemaId == schema.Id && ids.Contains(x.Id));
var contentItems = find.Not(x => x.DataText).ToListAsync(); var contentItems = find.ToListAsync();
var contentCount = find.CountDocumentsAsync(); var contentCount = find.CountDocumentsAsync();
await Task.WhenAll(contentItems, contentCount); await Task.WhenAll(contentItems, contentCount);
@ -107,6 +110,66 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
return ResultList.Create<IContentEntity>(contentCount.Result, contentItems.Result); return ResultList.Create<IContentEntity>(contentCount.Result, contentItems.Result);
} }
public async Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id, Status[] status = null)
{
var find = Collection.Find(FilterFactory.Build(schema.Id, id, status));
var contentEntity = await find.FirstOrDefaultAsync();
contentEntity?.ParseData(schema.SchemaDef, Serializer);
return contentEntity;
}
public Task QueryScheduledWithoutDataAsync(Instant now, Func<IContentEntity, Task> callback)
{
return Collection.Find(x => x.ScheduledAt < now && x.IsDeleted != true)
.Not(x => x.DataByIds)
.Not(x => x.DataDraftByIds)
.ForEachAsync(c =>
{
callback(c);
});
}
public async Task<IReadOnlyList<Guid>> QueryIdsAsync(Guid appId, ISchemaEntity schema, FilterNode filterNode)
{
var filter = filterNode.AdjustToModel(schema.SchemaDef, true).ToFilter(schema.Id);
var contentEntities =
await Collection.Find(filter).Only(x => x.Id)
.ToListAsync();
return contentEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList();
}
public async Task<IReadOnlyList<Guid>> QueryIdsAsync(Guid appId)
{
var contentEntities =
await Collection.Find(x => x.IndexedAppId == appId).Only(x => x.Id)
.ToListAsync();
return contentEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList();
}
public async Task<(ContentState Value, long Version)> ReadAsync(Guid key, Func<Guid, Guid, Task<ISchemaEntity>> getSchema)
{
var contentEntity =
await Collection.Find(x => x.Id == key)
.FirstOrDefaultAsync();
if (contentEntity != null)
{
var schema = await getSchema(contentEntity.IndexedAppId, contentEntity.IndexedSchemaId);
contentEntity.ParseData(schema.SchemaDef, Serializer);
return (SimpleMapper.Map(contentEntity, new ContentState()), contentEntity.Version);
}
return (null, EtagVersion.NotFound);
}
public Task CleanupAsync(Guid id) public Task CleanupAsync(Guid id)
{ {
return Collection.UpdateManyAsync( return Collection.UpdateManyAsync(
@ -120,5 +183,31 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
return Collection.DeleteOneAsync(x => x.Id == id); return Collection.DeleteOneAsync(x => x.Id == id);
} }
public async Task UpsertAsync(MongoContentEntity content, long oldVersion)
{
try
{
await Collection.ReplaceOneAsync(x => x.Id == content.Id && x.Version == oldVersion, content, Upsert);
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await Collection.Find(x => x.Id == content.Id).Only(x => x.Id, x => x.Version)
.FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion["vs"].AsInt64, oldVersion, ex);
}
}
else
{
throw;
}
}
}
} }
} }

153
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentDraftCollection.cs

@ -1,153 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using NodaTime;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.States;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
internal sealed class MongoContentDraftCollection : MongoContentCollection
{
private readonly MongoDbOptions options;
public MongoContentDraftCollection(IMongoDatabase database, IJsonSerializer serializer, IOptions<MongoDbOptions> options)
: base(database, serializer, "State_Content_Draft")
{
this.options = options.Value;
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default)
{
await collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoContentEntity>(
Index
.Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.Id)
.Ascending(x => x.IsDeleted)), null, ct);
if (!options.IsCosmosDb)
{
await collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoContentEntity>(
Index
.Text(x => x.DataText)
.Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.Status)), null, ct);
}
await base.SetupCollectionAsync(collection, ct);
}
public async Task<IReadOnlyList<Guid>> QueryIdsAsync(Guid appId, ISchemaEntity schema, FilterNode filterNode)
{
var filter = filterNode.AdjustToModel(schema.SchemaDef, true).ToFilter(schema.Id);
var contentEntities =
await Collection.Find(filter).Only(x => x.Id)
.ToListAsync();
return contentEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList();
}
public async Task<IReadOnlyList<Guid>> QueryIdsAsync(Guid appId)
{
var contentEntities =
await Collection.Find(x => x.IndexedAppId == appId).Only(x => x.Id)
.ToListAsync();
return contentEntities.Select(x => Guid.Parse(x["_id"].AsString)).ToList();
}
public Task QueryScheduledWithoutDataAsync(Instant now, Func<IContentEntity, Task> callback)
{
return Collection.Find(x => x.ScheduledAt < now && x.IsDeleted != true)
.Not(x => x.DataByIds)
.Not(x => x.DataDraftByIds)
.Not(x => x.DataText)
.ForEachAsync(c =>
{
callback(c);
});
}
public async Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id)
{
var contentEntity =
await Collection.Find(x => x.IndexedSchemaId == schema.Id && x.Id == id && x.IsDeleted != true).Not(x => x.DataText)
.FirstOrDefaultAsync();
contentEntity?.ParseData(schema.SchemaDef, Serializer);
return contentEntity;
}
public async Task<(ContentState Value, long Version)> ReadAsync(Guid key, Func<Guid, Guid, Task<ISchemaEntity>> getSchema)
{
var contentEntity =
await Collection.Find(x => x.Id == key).Not(x => x.DataText)
.FirstOrDefaultAsync();
if (contentEntity != null)
{
var schema = await getSchema(contentEntity.IndexedAppId, contentEntity.IndexedSchemaId);
contentEntity.ParseData(schema.SchemaDef, Serializer);
return (SimpleMapper.Map(contentEntity, new ContentState()), contentEntity.Version);
}
return (null, EtagVersion.NotFound);
}
public async Task UpsertAsync(MongoContentEntity content, long oldVersion)
{
try
{
content.DataText = content.DataDraftByIds.ToFullText();
await Collection.ReplaceOneAsync(x => x.Id == content.Id && x.Version == oldVersion, content, Upsert);
}
catch (MongoWriteException ex)
{
if (ex.WriteError.Category == ServerErrorCategory.DuplicateKey)
{
var existingVersion =
await Collection.Find(x => x.Id == content.Id).Only(x => x.Id, x => x.Version)
.FirstOrDefaultAsync();
if (existingVersion != null)
{
throw new InconsistentStateException(existingVersion["vs"].AsInt64, oldVersion, ex);
}
}
else
{
throw;
}
}
}
}
}

4
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentEntity.cs

@ -69,10 +69,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
[BsonJson] [BsonJson]
public ScheduleJob ScheduleJob { get; set; } public ScheduleJob ScheduleJob { get; set; }
[BsonIgnoreIfDefault]
[BsonElement("dt")]
public string DataText { get; set; }
[BsonRequired] [BsonRequired]
[BsonElement("ai")] [BsonElement("ai")]
public NamedId<Guid> AppId { get; set; } public NamedId<Guid> AppId { get; set; }

67
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentPublishedCollection.cs

@ -1,67 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.ConvertContent;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.MongoDb;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{
internal sealed class MongoContentPublishedCollection : MongoContentCollection
{
private readonly MongoDbOptions options;
public MongoContentPublishedCollection(IMongoDatabase database, IJsonSerializer serializer, IOptions<MongoDbOptions> options)
: base(database, serializer, "State_Content_Published")
{
this.options = options.Value;
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection, CancellationToken ct = default)
{
await collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoContentEntity>(Index.Ascending(x => x.IndexedSchemaId).Ascending(x => x.Id)), null, ct);
if (!options.IsCosmosDb)
{
await collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoContentEntity>(Index.Text(x => x.DataText).Ascending(x => x.IndexedSchemaId)), null, ct);
}
await base.SetupCollectionAsync(collection, ct);
}
public async Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Guid id)
{
var contentEntity =
await Collection.Find(x => x.IndexedSchemaId == schema.Id && x.Id == id).Not(x => x.DataText)
.FirstOrDefaultAsync();
contentEntity?.ParseData(schema.SchemaDef, Serializer);
return contentEntity;
}
public Task UpsertAsync(MongoContentEntity content)
{
content.DataText = content.DataByIds.ToFullText();
content.DataDraftByIds = null;
content.ScheduleJob = null;
content.ScheduledAt = null;
return Collection.ReplaceOneAsync(x => x.Id == content.Id, content, new UpdateOptions { IsUpsert = true });
}
}
}

54
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs

@ -9,18 +9,17 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using MongoDB.Driver; using MongoDB.Driver;
using NodaTime; using NodaTime;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Domain.Apps.Entities.Contents.Text;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Log;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
@ -30,28 +29,26 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
private readonly IMongoDatabase database; private readonly IMongoDatabase database;
private readonly IAppProvider appProvider; private readonly IAppProvider appProvider;
private readonly IJsonSerializer serializer; private readonly IJsonSerializer serializer;
private readonly MongoContentDraftCollection contentsDraft; private readonly ITextIndexer indexer;
private readonly MongoContentPublishedCollection contentsPublished; private readonly MongoContentCollection contents;
public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider, IJsonSerializer serializer, IOptions<MongoDbOptions> options) public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider, IJsonSerializer serializer, ITextIndexer indexer)
{ {
Guard.NotNull(appProvider, nameof(appProvider)); Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(serializer, nameof(serializer)); Guard.NotNull(serializer, nameof(serializer));
Guard.NotNull(options, nameof(options)); Guard.NotNull(indexer, nameof(ITextIndexer));
this.appProvider = appProvider; this.appProvider = appProvider;
this.database = database;
this.indexer = indexer;
this.serializer = serializer; this.serializer = serializer;
contentsDraft = new MongoContentDraftCollection(database, serializer, options); contents = new MongoContentCollection(database, serializer);
contentsPublished = new MongoContentPublishedCollection(database, serializer, options);
this.database = database;
} }
public Task InitializeAsync(CancellationToken ct = default) public Task InitializeAsync(CancellationToken ct = default)
{ {
return Task.WhenAll(contentsDraft.InitializeAsync(ct), contentsPublished.InitializeAsync(ct)); return contents.InitializeAsync(ct);
} }
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Query query) public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Query query)
@ -60,11 +57,15 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
if (RequiresPublished(status)) if (RequiresPublished(status))
{ {
return await contentsPublished.QueryAsync(app, schema, query); var ids = await indexer.SearchAsync(query.FullText, app, schema);
return await contents.QueryAsync(app, schema, query, ids);
} }
else else
{ {
return await contentsDraft.QueryAsync(app, schema, query, status, true); var ids = await indexer.SearchAsync(query.FullText, app, schema, true);
return await contents.QueryAsync(app, schema, query, ids, status, true);
} }
} }
} }
@ -75,11 +76,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
if (RequiresPublished(status)) if (RequiresPublished(status))
{ {
return await contentsPublished.QueryAsync(app, schema, ids); return await contents.QueryAsync(app, schema, ids);
} }
else else
{ {
return await contentsDraft.QueryAsync(app, schema, ids, status); return await contents.QueryAsync(app, schema, ids, status);
} }
} }
} }
@ -90,11 +91,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
if (RequiresPublished(status)) if (RequiresPublished(status))
{ {
return await contentsPublished.FindContentAsync(app, schema, id); return await contents.FindContentAsync(app, schema, id);
} }
else else
{ {
return await contentsDraft.FindContentAsync(app, schema, id); return await contents.FindContentAsync(app, schema, id, status);
} }
} }
} }
@ -103,7 +104,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
using (Profiler.TraceMethod<MongoContentRepository>()) using (Profiler.TraceMethod<MongoContentRepository>())
{ {
return await contentsDraft.QueryIdsAsync(appId, await appProvider.GetSchemaAsync(appId, schemaId), filterNode); return await contents.QueryIdsAsync(appId, await appProvider.GetSchemaAsync(appId, schemaId), filterNode);
} }
} }
@ -111,7 +112,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
using (Profiler.TraceMethod<MongoContentRepository>()) using (Profiler.TraceMethod<MongoContentRepository>())
{ {
return await contentsDraft.QueryIdsAsync(appId); return await contents.QueryIdsAsync(appId);
} }
} }
@ -119,22 +120,13 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
using (Profiler.TraceMethod<MongoContentRepository>()) using (Profiler.TraceMethod<MongoContentRepository>())
{ {
await contentsDraft.QueryScheduledWithoutDataAsync(now, callback); await contents.QueryScheduledWithoutDataAsync(now, callback);
} }
} }
public Task RemoveAsync(Guid appId)
{
return Task.WhenAll(
contentsDraft.RemoveAsync(appId),
contentsPublished.RemoveAsync(appId));
}
public Task ClearAsync() public Task ClearAsync()
{ {
return Task.WhenAll( return contents.ClearAsync();
contentsDraft.ClearAsync(),
contentsPublished.ClearAsync());
} }
public Task DeleteArchiveAsync() public Task DeleteArchiveAsync()

8
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_EventHandling.cs

@ -33,16 +33,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
protected Task On(AssetDeleted @event) protected Task On(AssetDeleted @event)
{ {
return Task.WhenAll( return contents.CleanupAsync(@event.AssetId);
contentsDraft.CleanupAsync(@event.AssetId),
contentsPublished.CleanupAsync(@event.AssetId));
} }
protected Task On(ContentDeleted @event) protected Task On(ContentDeleted @event)
{ {
return Task.WhenAll( return contents.CleanupAsync(@event.ContentId);
contentsDraft.CleanupAsync(@event.ContentId),
contentsPublished.CleanupAsync(@event.ContentId));
} }
Task IEventConsumer.ClearAsync() Task IEventConsumer.ClearAsync()

14
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository_SnapshotStore.cs

@ -7,7 +7,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.State; using Squidex.Domain.Apps.Entities.Contents.State;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
@ -23,7 +22,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
{ {
using (Profiler.TraceMethod<MongoContentRepository>()) using (Profiler.TraceMethod<MongoContentRepository>())
{ {
return await contentsDraft.ReadAsync(key, GetSchemaAsync); return await contents.ReadAsync(key, GetSchemaAsync);
} }
} }
@ -58,16 +57,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
Version = newVersion Version = newVersion
}); });
await contentsDraft.UpsertAsync(content, oldVersion); await contents.UpsertAsync(content, oldVersion);
if (value.Status == Status.Published && !value.IsDeleted) await indexer.IndexAsync(value.SchemaId.Id, value.Id, value.Data, value.DataDraft);
{
await contentsPublished.UpsertAsync(content);
}
else
{
await contentsPublished.RemoveAsync(content.Id);
}
} }
} }

44
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs → src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs

@ -19,7 +19,7 @@ using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
{ {
public static class FindExtensions public static class FilterFactory
{ {
private static readonly FilterDefinitionBuilder<MongoContentEntity> Filter = Builders<MongoContentEntity>.Filter; private static readonly FilterDefinitionBuilder<MongoContentEntity> Filter = Builders<MongoContentEntity>.Filter;
@ -109,7 +109,22 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
return cursor.Skip(query); return cursor.Skip(query);
} }
public static FilterDefinition<MongoContentEntity> ToFilter(this Query query, Guid schemaId, Status[] status) public static FilterDefinition<MongoContentEntity> Build(Guid schemaId, Guid id, Status[] status)
{
return CreateFilter(schemaId, new List<Guid> { id }, status, null);
}
public static FilterDefinition<MongoContentEntity> Build(Guid schemaId, ICollection<Guid> ids, Status[] status)
{
return CreateFilter(schemaId, ids, status, null);
}
public static FilterDefinition<MongoContentEntity> ToFilter(this Query query, Guid schemaId, ICollection<Guid> ids, Status[] status)
{
return CreateFilter(schemaId, ids, status, query);
}
private static FilterDefinition<MongoContentEntity> CreateFilter(Guid schemaId, ICollection<Guid> ids, Status[] status, Query query)
{ {
var filters = new List<FilterDefinition<MongoContentEntity>> var filters = new List<FilterDefinition<MongoContentEntity>>
{ {
@ -117,25 +132,28 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
Filter.Ne(x => x.IsDeleted, true) Filter.Ne(x => x.IsDeleted, true)
}; };
if (status != null) if (ids != null && ids.Count > 0)
{
filters.Add(Filter.In(x => x.Status, status));
}
var filter = query.BuildFilter<MongoContentEntity>();
if (filter.Filter != null)
{ {
if (filter.Last) if (ids.Count > 1)
{ {
filters.Add(filter.Filter); filters.Add(Filter.In(x => x.Id, ids));
} }
else else
{ {
filters.Insert(0, filter.Filter); filters.Add(Filter.Eq(x => x.Id, ids.First()));
} }
} }
if (status != null)
{
filters.Add(Filter.In(x => x.Status, status));
}
if (query.Filter != null)
{
filters.Add(query.Filter.BuildFilter<MongoContentEntity>());
}
return Filter.And(filters); return Filter.And(filters);
} }

2
src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs

@ -28,7 +28,5 @@ namespace Squidex.Domain.Apps.Entities.Contents.Repositories
Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Guid id); Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Guid id);
Task QueryScheduledWithoutDataAsync(Instant now, Func<IContentEntity, Task> callback); Task QueryScheduledWithoutDataAsync(Instant now, Func<IContentEntity, Task> callback);
Task RemoveAsync(Guid appId);
} }
} }

76
src/Squidex.Domain.Apps.Entities/Contents/Text/GrainTextIndexer.cs

@ -0,0 +1,76 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Orleans;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Contents.Text
{
public sealed class GrainTextIndexer : ITextIndexer
{
private readonly IGrainFactory grainFactory;
public GrainTextIndexer(IGrainFactory grainFactory)
{
Guard.NotNull(grainFactory, nameof(grainFactory));
this.grainFactory = grainFactory;
}
public Task DeleteAsync(Guid schemaId, Guid id)
{
var index = grainFactory.GetGrain<ITextIndexerGrain>(schemaId);
return index.DeleteAsync(id);
}
public async Task IndexAsync(Guid schemaId, Guid id, NamedContentData data, NamedContentData dataDraft)
{
var index = grainFactory.GetGrain<ITextIndexerGrain>(schemaId);
if (data != null)
{
await index.IndexAsync(id, new IndexData { });
}
if (dataDraft != null)
{
await index.IndexAsync(id, new IndexData { IsDraft = true });
}
}
public async Task<List<Guid>> SearchAsync(string queryText, IAppEntity app, ISchemaEntity schema, bool useDraft = false)
{
if (string.IsNullOrWhiteSpace(queryText))
{
return null;
}
var index = grainFactory.GetGrain<ITextIndexerGrain>(schema.Id);
var languages = app.LanguagesConfig.Select(x => x.Key).ToList();
var context = new SearchContext
{
AppVersion = app.Version,
Schema = schema.SchemaDef,
SchemaVersion = schema.Version,
Languages = languages,
IsDraft = useDraft
};
return await index.SearchAsync(queryText, context);
}
}
}

25
src/Squidex.Domain.Apps.Entities/Contents/Text/ITextIndexer.cs

@ -0,0 +1,25 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Schemas;
namespace Squidex.Domain.Apps.Entities.Contents.Text
{
public interface ITextIndexer
{
Task DeleteAsync(Guid schemaId, Guid id);
Task IndexAsync(Guid schemaId, Guid id, NamedContentData data, NamedContentData dataDraft);
Task<List<Guid>> SearchAsync(string queryText, IAppEntity appEntity, ISchemaEntity schemaEntity, bool useDraft = false);
}
}

24
src/Squidex.Domain.Apps.Entities/Contents/Text/ITextIndexerGrain.cs

@ -0,0 +1,24 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Orleans;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Contents.Text
{
public interface ITextIndexerGrain : IGrainWithGuidKey
{
Task DeleteAsync(Guid id);
Task IndexAsync(Guid id, J<IndexData> data);
Task<List<Guid>> SearchAsync(string queryText, J<SearchContext> context);
}
}

18
src/Squidex.Domain.Apps.Entities/Contents/Text/IndexData.cs

@ -0,0 +1,18 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.Contents;
namespace Squidex.Domain.Apps.Entities.Contents.Text
{
public sealed class IndexData
{
public NamedContentData Data { get; set; }
public bool IsDraft { get; set; }
}
}

23
src/Squidex.Domain.Apps.Entities/Contents/Text/SearchContext.cs

@ -0,0 +1,23 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.Schemas;
namespace Squidex.Domain.Apps.Entities.Contents.Text
{
public sealed class SearchContext
{
public bool IsDraft { get; set; }
public long AppVersion { get; set; }
public long SchemaVersion { get; set; }
public List<string> Languages { get; set; }
}
}

47
src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexer.cs

@ -1,47 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading.Tasks;
using Squidex.Domain.Apps.Events.Contents;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Entities.Contents.Text
{
public sealed class TextIndexer : IEventConsumer
{
public string Name
{
get { return GetType().Name; }
}
public string EventsFilter
{
get { return "^content-"; }
}
public Task ClearAsync()
{
return TaskHelper.Done;
}
public Task On(Envelope<IEvent> @event)
{
switch (@event.Payload)
{
case ContentCreated contentCreated:
break;
case ContentUpdated contentUpdated:
break;
case ContentUpdateProposed contentUpdateProposed:
break;
}
return Task.CompletedTask;
}
}
}

187
src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs

@ -8,18 +8,16 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Lucene.Net.Analysis; using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents; using Lucene.Net.Documents;
using Lucene.Net.Index; using Lucene.Net.Index;
using Lucene.Net.QueryParsers.Classic; using Lucene.Net.QueryParsers.Classic;
using Lucene.Net.Search; using Lucene.Net.Search;
using Lucene.Net.Store; using Lucene.Net.Store;
using Lucene.Net.Util; using Lucene.Net.Util;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Assets; using Squidex.Infrastructure.Assets;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
@ -27,7 +25,7 @@ using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Contents.Text namespace Squidex.Domain.Apps.Entities.Contents.Text
{ {
public sealed class TextIndexerGrain : GrainOfGuid public sealed class TextIndexerGrain : GrainOfGuid, ITextIndexerGrain
{ {
private const LuceneVersion Version = LuceneVersion.LUCENE_48; private const LuceneVersion Version = LuceneVersion.LUCENE_48;
private const int MaxResults = 2000; private const int MaxResults = 2000;
@ -39,9 +37,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text
private IndexWriter indexWriter; private IndexWriter indexWriter;
private IndexReader indexReader; private IndexReader indexReader;
private QueryParser queryParser; private QueryParser queryParser;
private int currentAppVersion; private long currentAppVersion;
private int currentSchemaVersion; private long currentSchemaVersion;
private int updates; private long updates;
public TextIndexerGrain(IAssetStore assetStore) public TextIndexerGrain(IAssetStore assetStore)
{ {
@ -72,147 +70,133 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text
indexReader = indexWriter.GetReader(true); indexReader = indexWriter.GetReader(true);
} }
public Task DeleteContentAsync(Guid id) public Task DeleteAsync(Guid id)
{ {
indexWriter.DeleteDocuments(new Term("id", id.ToString())); indexWriter.DeleteDocuments(new Term("id", id.ToString()));
return TryFlushAsync(); return TryFlushAsync();
} }
public Task AddContentAsync(Guid id, NamedContentData data, bool isUpdate, bool isDraft) public Task IndexAsync(Guid id, J<IndexData> data)
{ {
var idString = id.ToString(); string idString = id.ToString(), draft = data.Value.IsDraft.ToString();
if (isUpdate) indexWriter.DeleteDocuments(
{ new Term("id", idString),
indexWriter.DeleteDocuments(new Term("id", idString)); new Term("dd", draft));
}
var document = new Document(); var document = new Document();
document.AddStringField("id", idString, Field.Store.YES); document.AddStringField("id", idString, Field.Store.YES);
document.AddInt32Field("draft", isDraft ? 1 : 0, Field.Store.YES); document.AddStringField("dd", draft, Field.Store.YES);
foreach (var field in data) var languages = new Dictionary<string, StringBuilder>();
void AppendText(string language, string text)
{ {
foreach (var fieldValue in field.Value) if (!string.IsNullOrWhiteSpace(text))
{ {
var value = fieldValue.Value; var sb = languages.GetOrAddNew(language);
if (value.Type == JsonValueType.String) if (sb.Length > 0)
{ {
var fieldName = BuildFieldName(fieldValue.Key, field.Key); sb.Append(" ");
document.AddTextField(fieldName, fieldValue.Value.ToString(), Field.Store.YES);
} }
else if (value.Type == JsonValueType.Object)
{
foreach (var property in (JsonObject)value)
{
if (property.Value.Type == JsonValueType.String)
{
var fieldName = BuildFieldName(fieldValue.Key, field.Key, property.Key);
document.AddTextField(fieldName, property.Value.ToString(), Field.Store.YES); sb.Append(text);
} }
} }
}
foreach (var field in data.Value.Data)
{
foreach (var fieldValue in field.Value)
{
var appendText = new Action<string>(text => AppendText(fieldValue.Key, text));
AppendJsonText(fieldValue.Value, appendText);
} }
} }
foreach (var field in languages)
{
document.AddTextField(field.Key, field.Value.ToString(), Field.Store.NO);
}
indexWriter.AddDocument(document); indexWriter.AddDocument(document);
return TryFlushAsync(); return TryFlushAsync();
} }
public Task<List<Guid>> SearchAsync(string term, int appVersion, int schemaVersion, J<Schema> schema, List<string> languages) private static void AppendJsonText(IJsonValue value, Action<string> appendText)
{ {
var query = BuildQuery(term, appVersion, schemaVersion, schema, languages); if (value.Type == JsonValueType.String)
{
var text = value.ToString();
var result = new List<Guid>(); appendText(text);
}
else if (value is JsonArray array)
{
foreach (var item in array)
{
AppendJsonText(item, appendText);
}
}
else if (value is JsonObject obj)
{
foreach (var item in obj.Values)
{
AppendJsonText(item, appendText);
}
}
}
if (indexReader != null) public Task<List<Guid>> SearchAsync(string queryText, J<SearchContext> context)
{
var result = new HashSet<Guid>();
if (!string.IsNullOrWhiteSpace(queryText))
{ {
var hits = new IndexSearcher(indexReader).Search(query, MaxResults).ScoreDocs; var query = BuildQuery(queryText, context);
foreach (var hit in hits) if (indexReader != null)
{ {
var document = indexReader.Document(hit.Doc); var filter = new QueryWrapperFilter(new TermQuery(new Term("dd", context.Value.IsDraft.ToString())));
var idField = document.GetField("id")?.GetStringValue(); var hits = new IndexSearcher(indexReader).Search(query, MaxResults).ScoreDocs;
if (idField != null && Guid.TryParse(idField, out var guid)) foreach (var hit in hits)
{ {
result.Add(guid); var document = indexReader.Document(hit.Doc);
var idField = document.GetField("id")?.GetStringValue();
if (idField != null && Guid.TryParse(idField, out var guid))
{
result.Add(guid);
}
} }
} }
} }
return Task.FromResult(result); return Task.FromResult(result.ToList());
} }
private Query BuildQuery(string query, int appVersion, int schemaVersion, J<Schema> schema, List<string> language) private Query BuildQuery(string query, SearchContext context)
{ {
if (queryParser == null || currentAppVersion != appVersion || currentSchemaVersion != schemaVersion) if (queryParser == null || currentAppVersion != context.AppVersion || currentSchemaVersion != context.SchemaVersion)
{ {
var fields = BuildFields(schema, language); var fields = context.Languages.Select(BuildFieldName).ToArray();
queryParser = new MultiFieldQueryParser(Version, fields, Analyzer); queryParser = new MultiFieldQueryParser(Version, fields, Analyzer);
currentAppVersion = appVersion; currentAppVersion = context.AppVersion;
currentSchemaVersion = schemaVersion; currentSchemaVersion = context.SchemaVersion;
} }
return queryParser.Parse(query); return queryParser.Parse(query);
} }
private string[] BuildFields(Schema schema, IEnumerable<string> languages)
{
var fieldNames = new List<string>();
var iv = InvariantPartitioning.Instance.Master.Key;
foreach (var field in schema.Fields)
{
if (field.RawProperties is StringFieldProperties)
{
if (field.Partitioning.Equals(Partitioning.Invariant))
{
fieldNames.Add(BuildFieldName(iv, field.Name));
}
else
{
foreach (var language in languages)
{
fieldNames.Add(BuildFieldName(language, field.Name));
}
}
}
else if (field is IArrayField arrayField)
{
foreach (var nested in arrayField.Fields)
{
if (nested.RawProperties is StringFieldProperties)
{
if (field.Partitioning.Equals(Partitioning.Invariant))
{
fieldNames.Add(BuildFieldName(iv, field.Name, nested.Name));
}
else
{
foreach (var language in languages)
{
fieldNames.Add(BuildFieldName(language, field.Name, nested.Name));
}
}
}
}
}
}
return fieldNames.ToArray();
}
private async Task TryFlushAsync() private async Task TryFlushAsync()
{ {
updates++; updates++;
@ -255,14 +239,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text
} }
} }
private static string BuildFieldName(string language, string name) private static string BuildFieldName(string language)
{
return $"{language}_{name}";
}
private static string BuildFieldName(string language, string name, string nested)
{ {
return $"{language}_{name}_{nested}"; return $"field_{language}";
} }
} }
} }

4
src/Squidex/Config/Domain/EntitiesServices.cs

@ -31,6 +31,7 @@ using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands; using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.Edm; using Squidex.Domain.Apps.Entities.Contents.Edm;
using Squidex.Domain.Apps.Entities.Contents.GraphQL; using Squidex.Domain.Apps.Entities.Contents.GraphQL;
using Squidex.Domain.Apps.Entities.Contents.Text;
using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Entities.History;
using Squidex.Domain.Apps.Entities.Rules; using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Domain.Apps.Entities.Rules.Commands; using Squidex.Domain.Apps.Entities.Rules.Commands;
@ -105,6 +106,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<GrainTagService>() services.AddSingletonAs<GrainTagService>()
.As<ITagService>(); .As<ITagService>();
services.AddSingletonAs<GrainTextIndexer>()
.As<ITextIndexer>();
services.AddSingletonAs<FileTypeTagGenerator>() services.AddSingletonAs<FileTypeTagGenerator>()
.As<ITagGenerator<CreateAsset>>(); .As<ITagGenerator<CreateAsset>>();

44
tests/Squidex.Domain.Apps.Entities.Tests/Contents/Text/TextIndexerGrainTests.cs

@ -22,15 +22,23 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text
new Schema("test") new Schema("test")
.AddString(1, "test", Partitioning.Invariant) .AddString(1, "test", Partitioning.Invariant)
.AddString(2, "localized", Partitioning.Language); .AddString(2, "localized", Partitioning.Language);
private readonly List<string> languages = new List<string> { "de", "en" };
private readonly Guid schemaId = Guid.NewGuid(); private readonly Guid schemaId = Guid.NewGuid();
private readonly List<Guid> ids1 = new List<Guid> { Guid.NewGuid() }; private readonly List<Guid> ids1 = new List<Guid> { Guid.NewGuid() };
private readonly List<Guid> ids2 = new List<Guid> { Guid.NewGuid() }; private readonly List<Guid> ids2 = new List<Guid> { Guid.NewGuid() };
private readonly SearchContext context;
private readonly IAssetStore assetStore = new MemoryAssetStore(); private readonly IAssetStore assetStore = new MemoryAssetStore();
private readonly TextIndexerGrain sut; private readonly TextIndexerGrain sut;
public TextIndexerGrainTests() public TextIndexerGrainTests()
{ {
context = new SearchContext
{
AppVersion = 1,
Schema = schema,
SchemaVersion = 1,
Languages = new List<string> { "de", "en" }
};
sut = new TextIndexerGrain(assetStore); sut = new TextIndexerGrain(assetStore);
sut.ActivateAsync(schemaId).Wait(); sut.ActivateAsync(schemaId).Wait();
} }
@ -52,11 +60,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text
{ {
await other.ActivateAsync(schemaId); await other.ActivateAsync(schemaId);
var helloIds = await other.SearchAsync("Hello", 0, 0, schema, languages); var helloIds = await other.SearchAsync("Hello", context);
Assert.Equal(ids1, helloIds); Assert.Equal(ids1, helloIds);
var worldIds = await other.SearchAsync("World", 0, 0, schema, languages); var worldIds = await other.SearchAsync("World", context);
Assert.Equal(ids2, worldIds); Assert.Equal(ids2, worldIds);
} }
@ -71,11 +79,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text
{ {
await AddInvariantContent(); await AddInvariantContent();
var helloIds = await sut.SearchAsync("Hello", 0, 0, schema, languages); var helloIds = await sut.SearchAsync("Hello", context);
Assert.Equal(ids1, helloIds); Assert.Equal(ids1, helloIds);
var worldIds = await sut.SearchAsync("World", 0, 0, schema, languages); var worldIds = await sut.SearchAsync("World", context);
Assert.Equal(ids2, worldIds); Assert.Equal(ids2, worldIds);
} }
@ -85,14 +93,14 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text
{ {
await AddInvariantContent(); await AddInvariantContent();
await sut.DeleteContentAsync(ids1[0]); await sut.DeleteAsync(ids1[0]);
await sut.FlushAsync(); await sut.FlushAsync();
var helloIds = await sut.SearchAsync("Hello", 0, 0, schema, languages); var helloIds = await sut.SearchAsync("Hello", context);
Assert.Empty(helloIds); Assert.Empty(helloIds);
var worldIds = await sut.SearchAsync("World", 0, 0, schema, languages); var worldIds = await sut.SearchAsync("World", context);
Assert.Equal(ids2, worldIds); Assert.Equal(ids2, worldIds);
} }
@ -102,20 +110,20 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text
{ {
await AddLocalizedContent(); await AddLocalizedContent();
var german1 = await sut.SearchAsync("Stadt", 0, 0, schema, languages); var german1 = await sut.SearchAsync("Stadt", context);
var german2 = await sut.SearchAsync("and", 0, 0, schema, languages); var german2 = await sut.SearchAsync("and", context);
var germanStopwordsIds = await sut.SearchAsync("und", 0, 0, schema, languages); var germanStopwordsIds = await sut.SearchAsync("und", context);
Assert.Equal(ids1, german1); Assert.Equal(ids1, german1);
Assert.Equal(ids1, german2); Assert.Equal(ids1, german2);
Assert.Equal(ids2, germanStopwordsIds); Assert.Equal(ids2, germanStopwordsIds);
var english1 = await sut.SearchAsync("City", 0, 0, schema, languages); var english1 = await sut.SearchAsync("City", context);
var english2 = await sut.SearchAsync("und", 0, 0, schema, languages); var english2 = await sut.SearchAsync("und", context);
var englishStopwordsIds = await sut.SearchAsync("and", 0, 0, schema, languages); var englishStopwordsIds = await sut.SearchAsync("and", context);
Assert.Equal(ids2, english1); Assert.Equal(ids2, english1);
Assert.Equal(ids2, english2); Assert.Equal(ids2, english2);
@ -137,8 +145,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text
new ContentFieldData() new ContentFieldData()
.AddValue("en", "City and Surroundings und sonstiges")); .AddValue("en", "City and Surroundings und sonstiges"));
await sut.AddContentAsync(ids1[0], germanData, false, false); await sut.IndexAsync(ids1[0], new IndexData { Data = germanData });
await sut.AddContentAsync(ids2[0], englishData, false, false); await sut.IndexAsync(ids2[0], new IndexData { Data = englishData });
await sut.FlushAsync(); await sut.FlushAsync();
} }
@ -156,8 +164,8 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text
new ContentFieldData() new ContentFieldData()
.AddValue("iv", "World")); .AddValue("iv", "World"));
await sut.AddContentAsync(ids1[0], data1, false, false); await sut.IndexAsync(ids1[0], new IndexData { Data = data1 });
await sut.AddContentAsync(ids2[0], data2, false, false); await sut.IndexAsync(ids2[0], new IndexData { Data = data2 });
await sut.FlushAsync(); await sut.FlushAsync();
} }

Loading…
Cancel
Save