mirror of https://github.com/Squidex/squidex.git
Browse Source
* Storage improvements. * Temp * Dedicated Mongo collections. * Remove unused option. * Replace image names. * Use correct database. * Add caddypull/908/head
committed by
GitHub
38 changed files with 953 additions and 483 deletions
@ -0,0 +1,17 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.Infrastructure; |
|||
|
|||
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
|
|||
|
|||
namespace Squidex.Domain.Apps.Core.Contents |
|||
{ |
|||
public record struct ContentIdStatus(DomainId SchemaId, DomainId Id, Status Status) |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,182 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Concurrent; |
|||
using MongoDB.Driver; |
|||
using Squidex.Domain.Apps.Core.Contents; |
|||
using Squidex.Domain.Apps.Entities.Apps; |
|||
using Squidex.Domain.Apps.Entities.Contents; |
|||
using Squidex.Domain.Apps.Entities.Schemas; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
using Squidex.Infrastructure.MongoDb.Queries; |
|||
using Squidex.Infrastructure.Queries; |
|||
|
|||
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations |
|||
{ |
|||
internal sealed class QueryInDedicatedCollection : MongoBase<MongoContentEntity> |
|||
{ |
|||
private readonly ConcurrentDictionary<(DomainId, DomainId), Task<IMongoCollection<MongoContentEntity>>> collections = |
|||
new ConcurrentDictionary<(DomainId, DomainId), Task<IMongoCollection<MongoContentEntity>>>(); |
|||
|
|||
private readonly IMongoClient mongoClient; |
|||
private readonly string prefixDatabase; |
|||
private readonly string prefixCollection; |
|||
|
|||
public QueryInDedicatedCollection(IMongoClient mongoClient, string prefixDatabase, string prefixCollection) |
|||
{ |
|||
this.mongoClient = mongoClient; |
|||
this.prefixDatabase = prefixDatabase; |
|||
this.prefixCollection = prefixCollection; |
|||
} |
|||
|
|||
public Task<IMongoCollection<MongoContentEntity>> GetCollectionAsync(DomainId appId, DomainId schemaId) |
|||
{ |
|||
#pragma warning disable MA0106 // Avoid closure by using an overload with the 'factoryArgument' parameter
|
|||
return collections.GetOrAdd((appId, schemaId), async key => |
|||
{ |
|||
var (appId, schemaId) = key; |
|||
|
|||
var schemaDatabase = mongoClient.GetDatabase($"{prefixDatabase}_{appId}"); |
|||
var schemaCollection = schemaDatabase.GetCollection<MongoContentEntity>($"{prefixCollection}_{schemaId}"); |
|||
|
|||
await schemaCollection.Indexes.CreateManyAsync( |
|||
new[] |
|||
{ |
|||
new CreateIndexModel<MongoContentEntity>(Index |
|||
.Descending(x => x.LastModified) |
|||
.Ascending(x => x.Id) |
|||
.Ascending(x => x.IsDeleted) |
|||
.Ascending(x => x.ReferencedIds)), |
|||
new CreateIndexModel<MongoContentEntity>(Index |
|||
.Ascending(x => x.IndexedSchemaId) |
|||
.Ascending(x => x.IsDeleted) |
|||
.Descending(x => x.LastModified)) |
|||
}); |
|||
|
|||
return schemaCollection; |
|||
}); |
|||
#pragma warning restore MA0106 // Avoid closure by using an overload with the 'factoryArgument' parameter
|
|||
} |
|||
|
|||
public async Task<IReadOnlyList<ContentIdStatus>> QueryIdsAsync(IAppEntity app, ISchemaEntity schema, FilterNode<ClrValue> filterNode, |
|||
CancellationToken ct) |
|||
{ |
|||
// We need to translate the filter names to the document field names in MongoDB.
|
|||
var adjustedFilter = filterNode.AdjustToModel(app.Id); |
|||
|
|||
var filter = BuildFilter(adjustedFilter); |
|||
|
|||
var contentCollection = await GetCollectionAsync(schema.AppId.Id, schema.Id); |
|||
var contentEntities = await contentCollection.FindStatusAsync(filter, ct); |
|||
var contentResults = contentEntities.Select(x => new ContentIdStatus(x.IndexedSchemaId, x.Id, x.Status)).ToList(); |
|||
|
|||
return contentResults; |
|||
} |
|||
|
|||
public async Task<IResultList<IContentEntity>> QueryAsync(ISchemaEntity schema, Q q, |
|||
CancellationToken ct) |
|||
{ |
|||
// We need to translate the query names to the document field names in MongoDB.
|
|||
var query = q.Query.AdjustToModel(schema.AppId.Id); |
|||
|
|||
var filter = CreateFilter(query, q.Reference, q.CreatedBy); |
|||
|
|||
var contentCollection = await GetCollectionAsync(schema.AppId.Id, schema.Id); |
|||
var contentEntities = await contentCollection.QueryContentsAsync(filter, query, ct); |
|||
var contentTotal = (long)contentEntities.Count; |
|||
|
|||
if (contentTotal >= q.Query.Take || q.Query.Skip > 0) |
|||
{ |
|||
if (q.NoTotal || (q.NoSlowTotal && q.Query.Filter != null)) |
|||
{ |
|||
contentTotal = -1; |
|||
} |
|||
else if (query.IsSatisfiedByIndex()) |
|||
{ |
|||
// It is faster to filter with sorting when there is an index, because it forces the index to be used.
|
|||
contentTotal = await contentCollection.Find(filter).QuerySort(query).CountDocumentsAsync(ct); |
|||
} |
|||
else |
|||
{ |
|||
contentTotal = await contentCollection.Find(filter).CountDocumentsAsync(ct); |
|||
} |
|||
} |
|||
|
|||
return ResultList.Create<IContentEntity>(contentTotal, contentEntities); |
|||
} |
|||
|
|||
public async Task UpsertVersionedAsync(DomainId documentId, long oldVersion, MongoContentEntity value, |
|||
CancellationToken ct = default) |
|||
{ |
|||
var collection = await GetCollectionAsync(value.AppId.Id, value.SchemaId.Id); |
|||
|
|||
await collection.UpsertVersionedAsync(documentId, oldVersion, value.Version, value, ct); |
|||
} |
|||
|
|||
public async Task RemoveAsync(MongoContentEntity value, |
|||
CancellationToken ct = default) |
|||
{ |
|||
var collection = await GetCollectionAsync(value.AppId.Id, value.SchemaId.Id); |
|||
|
|||
await collection.DeleteOneAsync(x => x.DocumentId == value.DocumentId, null, ct); |
|||
} |
|||
|
|||
private static FilterDefinition<MongoContentEntity> BuildFilter(FilterNode<ClrValue>? filter) |
|||
{ |
|||
var filters = new List<FilterDefinition<MongoContentEntity>> |
|||
{ |
|||
Filter.Exists(x => x.LastModified), |
|||
Filter.Exists(x => x.Id) |
|||
}; |
|||
|
|||
if (filter?.HasField("dl") != true) |
|||
{ |
|||
filters.Add(Filter.Ne(x => x.IsDeleted, true)); |
|||
} |
|||
|
|||
if (filter != null) |
|||
{ |
|||
filters.Add(filter.BuildFilter<MongoContentEntity>()); |
|||
} |
|||
|
|||
return Filter.And(filters); |
|||
} |
|||
|
|||
private static FilterDefinition<MongoContentEntity> CreateFilter(ClrQuery? query, |
|||
DomainId referenced, RefToken? createdBy) |
|||
{ |
|||
var filters = new List<FilterDefinition<MongoContentEntity>> |
|||
{ |
|||
Filter.Gt(x => x.LastModified, default), |
|||
Filter.Gt(x => x.Id, default) |
|||
}; |
|||
|
|||
if (query?.HasFilterField("dl") != true) |
|||
{ |
|||
filters.Add(Filter.Ne(x => x.IsDeleted, true)); |
|||
} |
|||
|
|||
if (query?.Filter != null) |
|||
{ |
|||
filters.Add(query.Filter.BuildFilter<MongoContentEntity>()); |
|||
} |
|||
|
|||
if (referenced != default) |
|||
{ |
|||
filters.Add(Filter.AnyEq(x => x.ReferencedIds, referenced)); |
|||
} |
|||
|
|||
if (createdBy != null) |
|||
{ |
|||
filters.Add(Filter.Eq(x => x.CreatedBy, createdBy)); |
|||
} |
|||
|
|||
return Filter.And(filters); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using MongoDB.Driver; |
|||
|
|||
#pragma warning disable RECS0108 // Warns about static fields in generic types
|
|||
|
|||
namespace Squidex.Infrastructure.MongoDb |
|||
{ |
|||
public abstract class MongoBase<TEntity> |
|||
{ |
|||
protected static readonly FieldDefinitionBuilder<TEntity> FieldBuilder = |
|||
FieldDefinitionBuilder<TEntity>.Instance; |
|||
|
|||
protected static readonly FilterDefinitionBuilder<TEntity> Filter = |
|||
Builders<TEntity>.Filter; |
|||
|
|||
protected static readonly IndexKeysDefinitionBuilder<TEntity> Index = |
|||
Builders<TEntity>.IndexKeys; |
|||
|
|||
protected static readonly ProjectionDefinitionBuilder<TEntity> Projection = |
|||
Builders<TEntity>.Projection; |
|||
|
|||
protected static readonly SortDefinitionBuilder<TEntity> Sort = |
|||
Builders<TEntity>.Sort; |
|||
|
|||
protected static readonly UpdateDefinitionBuilder<TEntity> Update = |
|||
Builders<TEntity>.Update; |
|||
|
|||
protected static readonly BulkWriteOptions BulkUnordered = |
|||
new BulkWriteOptions { IsOrdered = true }; |
|||
|
|||
protected static readonly InsertManyOptions InsertUnordered = |
|||
new InsertManyOptions { IsOrdered = true }; |
|||
|
|||
protected static readonly ReplaceOptions UpsertReplace = |
|||
new ReplaceOptions { IsUpsert = true }; |
|||
|
|||
protected static readonly UpdateOptions Upsert = |
|||
new UpdateOptions { IsUpsert = true }; |
|||
|
|||
static MongoBase() |
|||
{ |
|||
TypeConverterStringSerializer<RefToken>.Register(); |
|||
|
|||
InstantSerializer.Register(); |
|||
|
|||
DomainIdSerializer.Register(); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue