Skip to content

Commit

Permalink
Custom indexes.
Browse files Browse the repository at this point in the history
  • Loading branch information
SebastianStehle committed Nov 2, 2024
1 parent 9987a06 commit 07816ae
Show file tree
Hide file tree
Showing 40 changed files with 1,440 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================

using System.Diagnostics.CodeAnalysis;
using MongoDB.Bson;
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations;
using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.States;

namespace Squidex.Domain.Apps.Entities.MongoDb.Contents;

public static class IndexParser
{
public static bool TryParse(BsonDocument source, string prefix, [MaybeNullWhen(false)] out IndexDefinition index)
{
index = null!;

if (!source.TryGetValue("name", out var name) || name.BsonType != BsonType.String)
{
return false;
}

if (!name.AsString.StartsWith(prefix, StringComparison.Ordinal))
{
return false;
}

if (!source.TryGetValue("key", out var keys) || keys.BsonType != BsonType.Document)
{
return false;
}

var definition = new IndexDefinition();
foreach (var property in keys.AsBsonDocument)
{
if (property.Value.BsonType != BsonType.Int32)
{
return false;
}

var fieldName = Adapt.MapPathReverse(property.Name).ToString();

var order = property.Value.AsInt32 < 0 ?
SortOrder.Descending :
SortOrder.Ascending;

definition.Add(new IndexField(fieldName, order));
}

if (definition.Count == 0)
{
return false;
}

index = definition;
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
Expand Down Expand Up @@ -318,4 +319,33 @@ public async Task AddCollectionsAsync(MongoContentEntity entity, Action<IMongoCo

add(Collection, entity);
}

public async Task CreateIndexAsync(DomainId appId, DomainId schemaId, IndexDefinition index,
CancellationToken ct = default)
{
if (queryInDedicatedCollection != null)
{
await queryInDedicatedCollection.CreateIndexAsync(appId, schemaId, index, ct);
}
}

public async Task DropIndexAsync(DomainId appId, DomainId schemaId, string name,
CancellationToken ct = default)
{
if (queryInDedicatedCollection != null)
{
await queryInDedicatedCollection.DropIndexAsync(appId, schemaId, name, ct);
}
}

public async Task<List<IndexDefinition>> GetIndexesAsync(DomainId appId, DomainId schemaId,
CancellationToken ct = default)
{
if (queryInDedicatedCollection != null)
{
return await queryInDedicatedCollection.GetIndexesAsync(appId, schemaId, ct);
}

return [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.Queries;
using Squidex.Infrastructure.States;

namespace Squidex.Domain.Apps.Entities.MongoDb.Contents;

Expand Down Expand Up @@ -130,6 +131,24 @@ public Task ResetScheduledAsync(DomainId appId, DomainId contentId, SearchScope
return GetCollection(SearchScope.All).ResetScheduledAsync(appId, contentId, ct);
}

public Task CreateIndexAsync(DomainId appId, DomainId schemaId, IndexDefinition index,
CancellationToken ct = default)
{
return GetCollection(SearchScope.All).CreateIndexAsync(appId, schemaId, index, ct);
}

public Task<List<IndexDefinition>> GetIndexesAsync(DomainId appId, DomainId schemaId,
CancellationToken ct = default)
{
return GetCollection(SearchScope.All).GetIndexesAsync(appId, schemaId, ct);
}

public Task DropIndexAsync(DomainId appId, DomainId schemaId, string name,
CancellationToken ct = default)
{
return GetCollection(SearchScope.All).DropIndexAsync(appId, schemaId, name, ct);
}

private MongoContentCollection GetCollection(SearchScope scope)
{
return scope == SearchScope.All ? collectionComplete : collectionPublished;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// ==========================================================================

using System.Runtime.CompilerServices;
using System.Xml.Linq;
using NodaTime;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
Expand Down Expand Up @@ -79,6 +80,24 @@ public IAsyncEnumerable<Content> StreamReferencing(DomainId appId, DomainId refe
return Shard(appId).StreamReferencing(appId, references, take, scope, ct);
}

public Task CreateIndexAsync(DomainId appId, DomainId schemaId, IndexDefinition index,
CancellationToken ct = default)
{
return Shard(appId).CreateIndexAsync(appId, schemaId, index, ct);
}

public Task DropIndexAsync(DomainId appId, DomainId schemaId, string name,
CancellationToken ct = default)
{
return Shard(appId).DropIndexAsync(appId, schemaId, name, ct);
}

public Task<List<IndexDefinition>> GetIndexesAsync(DomainId appId, DomainId schemaId,
CancellationToken ct = default)
{
return Shard(appId).GetIndexesAsync(appId, schemaId, ct);
}

public async IAsyncEnumerable<Content> StreamScheduledWithoutDataAsync(Instant now, SearchScope scope,
[EnumeratorCancellation] CancellationToken ct = default)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================

using GraphQL;
using MongoDB.Bson.Serialization;
using Squidex.Infrastructure;
using Squidex.Infrastructure.MongoDb;
Expand All @@ -16,7 +17,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations;
public static class Adapt
{
private static Dictionary<string, PropertyPath> pathMap;
private static Dictionary<string, PropertyPath> pathReverseMap;
private static Dictionary<string, string> propertyMap;
private static Dictionary<string, string> propertyReverseMap;

public static IReadOnlyDictionary<string, string> PropertyMap
{
Expand All @@ -28,13 +31,29 @@ public static IReadOnlyDictionary<string, string> PropertyMap
StringComparer.OrdinalIgnoreCase);
}

public static IReadOnlyDictionary<string, string> PropertyReverseMap
{
get => propertyReverseMap ??=
BsonClassMap.LookupClassMap(typeof(MongoContentEntity)).AllMemberMaps
.ToDictionary(
x => x.ElementName,
x => x.MemberName.ToCamelCase(),
StringComparer.OrdinalIgnoreCase);
}

public static IReadOnlyDictionary<string, PropertyPath> PathMap
{
get => pathMap ??= PropertyMap.ToDictionary(x => x.Key, x => (PropertyPath)x.Value);
}

public static IReadOnlyDictionary<string, PropertyPath> PathReverseMap
{
get => pathReverseMap ??= PropertyReverseMap.ToDictionary(x => x.Key, x => (PropertyPath)x.Value);
}

public static PropertyPath MapPath(PropertyPath path)
{
// Shortcut to prevent allocations for most used field names.
if (path.Count == 1 && PathMap.TryGetValue(path[0], out var mappedPath))
{
return mappedPath;
Expand All @@ -52,12 +71,40 @@ public static PropertyPath MapPath(PropertyPath path)

for (var i = 1; i < path.Count; i++)
{
// MongoDB does not accept all field names.
result[i] = result[i].UnescapeEdmField().JsonToBsonName().JsonEscape();
}

return result;
}

public static PropertyPath MapPathReverse(PropertyPath path)
{
// Shortcut to prevent allocations for most used field names.
if (path.Count == 1 && PathReverseMap.TryGetValue(path[0], out var mappedPath))
{
return mappedPath;
}

var result = new List<string>(path);

if (result.Count > 0)
{
if (PropertyReverseMap.TryGetValue(path[0], out var mapped))
{
result[0] = mapped;
}
}

for (var i = 1; i < path.Count; i++)
{
// MongoDB does not accept all field names.
result[i] = result[i].EscapeEdmField().BsonToJsonName().JsonUnescape().ToCamelCase();
}

return result;
}

public static ClrQuery AdjustToModel(this ClrQuery query, DomainId appId)
{
if (query.Filter != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// ==========================================================================

using System.Collections.Concurrent;
using MongoDB.Bson;
using MongoDB.Driver;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents;
Expand Down Expand Up @@ -141,6 +142,63 @@ public async Task RemoveAsync(IClientSessionHandle session, MongoContentEntity v
await collection.DeleteOneAsync(session, x => x.DocumentId == value.DocumentId, null, ct);
}

public async Task DropIndexAsync(DomainId appId, DomainId schemaId, string name,
CancellationToken ct)
{
var collection = await GetCollectionAsync(appId, schemaId);

await collection.Indexes.DropOneAsync(name, ct);
}

public async Task<List<IndexDefinition>> GetIndexesAsync(DomainId appId, DomainId schemaId,
CancellationToken ct = default)
{
var result = new List<IndexDefinition>();

var collection = await GetCollectionAsync(appId, schemaId);
var colIndexes = await collection.Indexes.ListAsync(ct);

foreach (var index in await colIndexes.ToListAsync(ct))
{
if (IndexParser.TryParse(index, "custom_", out var definition))
{
result.Add(definition);
}
}

return result;
}

public async Task CreateIndexAsync(DomainId appId, DomainId schemaId, IndexDefinition index,
CancellationToken ct)
{
var collection = await GetCollectionAsync(appId, schemaId);

var definition = Index.Combine(
index.Select(field =>
{
var path = Adapt.MapPath(field.Name).ToString();
if (field.Order == SortOrder.Ascending)
{
return Index.Ascending(path);
}
return Index.Descending(path);
}));

var name = $"custom_{index.ToName()}";

await collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoContentEntity>(
definition,
new CreateIndexOptions
{
Name = name,
}),
cancellationToken: ct);
}

private static FilterDefinition<MongoContentEntity> BuildFilter(FilterNode<ClrValue>? filter)
{
var filters = new List<FilterDefinition<MongoContentEntity>>
Expand Down
3 changes: 3 additions & 0 deletions backend/src/Squidex.Domain.Apps.Entities/Backup/BackupJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ public BackupJob(

public static JobRequest BuildRequest(RefToken actor, App app)
{
Guard.NotNull(actor);
Guard.NotNull(app);

return JobRequest.Create(
actor,
TaskName,
Expand Down
12 changes: 10 additions & 2 deletions backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ public RestoreJob(

public static JobRequest BuildRequest(RefToken actor, Uri url, string? appName)
{
Guard.NotNull(actor);
Guard.NotNull(url);

return JobRequest.Create(
actor,
TaskName,
Expand All @@ -92,9 +95,14 @@ public static JobRequest BuildRequest(RefToken actor, Uri url, string? appName)
public async Task RunAsync(JobRunContext context,
CancellationToken ct)
{
if (!context.Job.Arguments.TryGetValue(ArgUrl, out var urlValue) || !Uri.TryCreate(urlValue, UriKind.Absolute, out var url))
if (!context.Job.Arguments.TryGetValue(ArgUrl, out var urlValue))
{
throw new DomainException($"Argument '{ArgUrl}' missing.");
}

if (!Uri.TryCreate(urlValue, UriKind.Absolute, out var url))
{
throw new DomainException("Argument missing.");
throw new DomainException($"Argument '{ArgUrl}' is not a valid URL.");
}

var state = new State
Expand Down
Loading

0 comments on commit 07816ae

Please sign in to comment.