Browse Source

Generized json.

pull/391/head
Sebastian 7 years ago
parent
commit
c36e943bb1
  1. 4
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs
  2. 4
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs
  3. 6
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs
  4. 2
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
  5. 12
      src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs
  6. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs
  7. 4
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  8. 24
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/AdaptionVisitor.cs
  9. 19
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs
  10. 2
      src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs
  11. 10
      src/Squidex.Domain.Apps.Entities/Assets/Queries/FilterTagTransformer.cs
  12. 2
      src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs
  13. 2
      src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs
  14. 4
      src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs
  15. 10
      src/Squidex.Domain.Apps.Entities/Contents/Queries/FilterTagTransformer.cs
  16. 4
      src/Squidex.Domain.Apps.Entities/Contents/Repositories/IContentRepository.cs
  17. 4
      src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs
  18. 60
      src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterVisitor.cs
  19. 4
      src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/LimitExtensions.cs
  20. 2
      src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/SortBuilder.cs
  21. 87
      src/Squidex.Infrastructure/Queries/ClrFilter.cs
  22. 13
      src/Squidex.Infrastructure/Queries/ClrQuery.cs
  23. 140
      src/Squidex.Infrastructure/Queries/ClrValue.cs
  24. 2
      src/Squidex.Infrastructure/Queries/ClrValueType.cs
  25. 67
      src/Squidex.Infrastructure/Queries/CompareFilter.cs
  26. 2
      src/Squidex.Infrastructure/Queries/CompareOperator.cs
  27. 85
      src/Squidex.Infrastructure/Queries/FilterBuilder.cs
  28. 70
      src/Squidex.Infrastructure/Queries/FilterComparison.cs
  29. 45
      src/Squidex.Infrastructure/Queries/FilterJunction.cs
  30. 4
      src/Squidex.Infrastructure/Queries/FilterNode.cs
  31. 8
      src/Squidex.Infrastructure/Queries/FilterNodeVisitor.cs
  32. 149
      src/Squidex.Infrastructure/Queries/FilterValue.cs
  33. 39
      src/Squidex.Infrastructure/Queries/LogicalFilter.cs
  34. 2
      src/Squidex.Infrastructure/Queries/LogicalFilterType.cs
  35. 14
      src/Squidex.Infrastructure/Queries/NegateFilter.cs
  36. 46
      src/Squidex.Infrastructure/Queries/OData/ConstantWithTypeVisitor.cs
  37. 4
      src/Squidex.Infrastructure/Queries/OData/EdmModelExtensions.cs
  38. 2
      src/Squidex.Infrastructure/Queries/OData/FilterBuilder.cs
  39. 46
      src/Squidex.Infrastructure/Queries/OData/FilterVisitor.cs
  40. 4
      src/Squidex.Infrastructure/Queries/OData/LimitExtensions.cs
  41. 2
      src/Squidex.Infrastructure/Queries/OData/SortBuilder.cs
  42. 10
      src/Squidex.Infrastructure/Queries/PascalCasePathConverter.cs
  43. 52
      src/Squidex.Infrastructure/Queries/PropertyPath.cs
  44. 4
      src/Squidex.Infrastructure/Queries/Query.cs
  45. 7
      src/Squidex.Infrastructure/Queries/SortNode.cs
  46. 12
      src/Squidex.Infrastructure/Queries/TransformVisitor.cs
  47. 12
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs
  48. 42
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/MongoDbQueryTests.cs
  49. 6
      tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/FilterTagTransformerTests.cs
  50. 6
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs
  51. 45
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs
  52. 10
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/FilterTagTransformerTests.cs
  53. 2
      tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/AExtensions.cs
  54. 8
      tests/Squidex.Infrastructure.Tests/Queries/PascalCasePathConverterTests.cs

4
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ValidationContext.cs

@ -14,7 +14,7 @@ using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Core.ValidateContent namespace Squidex.Domain.Apps.Core.ValidateContent
{ {
public delegate Task<IReadOnlyList<Guid>> CheckContents(Guid schemaId, FilterNode filter); public delegate Task<IReadOnlyList<Guid>> CheckContents(Guid schemaId, FilterNode<ClrValue> filter);
public delegate Task<IReadOnlyList<IAssetInfo>> CheckAssets(IEnumerable<Guid> ids); public delegate Task<IReadOnlyList<IAssetInfo>> CheckAssets(IEnumerable<Guid> ids);
@ -84,7 +84,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
return new ValidationContext(contentId, schemaId, checkContent, checkAsset, propertyPath.Enqueue(property), IsOptional); return new ValidationContext(contentId, schemaId, checkContent, checkAsset, propertyPath.Enqueue(property), IsOptional);
} }
public Task<IReadOnlyList<Guid>> GetContentIdsAsync(Guid validatedSchemaId, FilterNode filter) public Task<IReadOnlyList<Guid>> GetContentIdsAsync(Guid validatedSchemaId, FilterNode<ClrValue> filter)
{ {
return checkContent(validatedSchemaId, filter); return checkContent(validatedSchemaId, filter);
} }

4
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs

@ -15,7 +15,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
public sealed class ReferencesValidator : IValidator public sealed class ReferencesValidator : IValidator
{ {
private static readonly IReadOnlyList<string> Path = new List<string> { "Id" }; private static readonly PropertyPath Path = "Id";
private readonly Guid schemaId; private readonly Guid schemaId;
@ -28,7 +28,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
{ {
if (value is ICollection<Guid> contentIds) if (value is ICollection<Guid> contentIds)
{ {
var filter = new FilterComparison(Path, FilterOperator.In, new FilterValue(contentIds.ToList())); var filter = ClrFilter.In(Path, contentIds.ToList());
var foundIds = await context.GetContentIdsAsync(schemaId, filter); var foundIds = await context.GetContentIdsAsync(schemaId, filter);

6
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs

@ -20,15 +20,15 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
if (value != null && (count == 0 || (count == 2 && context.Path.Last() == InvariantPartitioning.Key))) if (value != null && (count == 0 || (count == 2 && context.Path.Last() == InvariantPartitioning.Key)))
{ {
FilterNode filter = null; FilterNode<ClrValue> filter = null;
if (value is string s) if (value is string s)
{ {
filter = new FilterComparison(Path(context), FilterOperator.Equals, new FilterValue(s)); filter = ClrFilter.Eq(Path(context), s);
} }
else if (value is double d) else if (value is double d)
{ {
filter = new FilterComparison(Path(context), FilterOperator.Equals, new FilterValue(d)); filter = ClrFilter.Eq(Path(context), d);
} }
if (filter != null) if (filter != null)

2
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs

@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
ct); ct);
} }
public async Task<IResultList<IAssetEntity>> QueryAsync(Guid appId, Query query) public async Task<IResultList<IAssetEntity>> QueryAsync(Guid appId, ClrQuery query)
{ {
using (Profiler.TraceMethod<MongoAssetRepository>("QueryAsyncByQuery")) using (Profiler.TraceMethod<MongoAssetRepository>("QueryAsyncByQuery"))
{ {

12
src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs

@ -20,11 +20,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
{ {
private static readonly FilterDefinitionBuilder<MongoAssetEntity> Filter = Builders<MongoAssetEntity>.Filter; private static readonly FilterDefinitionBuilder<MongoAssetEntity> Filter = Builders<MongoAssetEntity>.Filter;
public static Query AdjustToModel(this Query query) public static ClrQuery AdjustToModel(this ClrQuery query)
{ {
if (query.Filter != null) if (query.Filter != null)
{ {
query.Filter = PascalCasePathConverter.Transform(query.Filter); query.Filter = PascalCasePathConverter<ClrValue>.Transform(query.Filter);
} }
query.Sort = query.Sort query.Sort = query.Sort
@ -37,22 +37,22 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
return query; return query;
} }
public static IFindFluent<MongoAssetEntity, MongoAssetEntity> AssetSort(this IFindFluent<MongoAssetEntity, MongoAssetEntity> cursor, Query query) public static IFindFluent<MongoAssetEntity, MongoAssetEntity> AssetSort(this IFindFluent<MongoAssetEntity, MongoAssetEntity> cursor, ClrQuery query)
{ {
return cursor.Sort(query.BuildSort<MongoAssetEntity>()); return cursor.Sort(query.BuildSort<MongoAssetEntity>());
} }
public static IFindFluent<MongoAssetEntity, MongoAssetEntity> AssetTake(this IFindFluent<MongoAssetEntity, MongoAssetEntity> cursor, Query query) public static IFindFluent<MongoAssetEntity, MongoAssetEntity> AssetTake(this IFindFluent<MongoAssetEntity, MongoAssetEntity> cursor, ClrQuery query)
{ {
return cursor.Take(query); return cursor.Take(query);
} }
public static IFindFluent<MongoAssetEntity, MongoAssetEntity> AssetSkip(this IFindFluent<MongoAssetEntity, MongoAssetEntity> cursor, Query query) public static IFindFluent<MongoAssetEntity, MongoAssetEntity> AssetSkip(this IFindFluent<MongoAssetEntity, MongoAssetEntity> cursor, ClrQuery query)
{ {
return cursor.Skip(query); return cursor.Skip(query);
} }
public static FilterDefinition<MongoAssetEntity> BuildFilter(this Query query, Guid appId) public static FilterDefinition<MongoAssetEntity> BuildFilter(this ClrQuery query, Guid appId)
{ {
var filters = new List<FilterDefinition<MongoAssetEntity>> var filters = new List<FilterDefinition<MongoAssetEntity>>
{ {

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

@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
return "State_Contents"; return "State_Contents";
} }
public async Task<IResultList<IContentEntity>> QueryAsync(ISchemaEntity schema, Query query, List<Guid> ids, Status[] status, bool inDraft, bool includeDraft = true) public async Task<IResultList<IContentEntity>> QueryAsync(ISchemaEntity schema, ClrQuery query, List<Guid> ids, Status[] status, bool inDraft, bool includeDraft = true)
{ {
try try
{ {
@ -169,7 +169,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
}); });
} }
public async Task<IReadOnlyList<Guid>> QueryIdsAsync(ISchemaEntity schema, FilterNode filterNode) public async Task<IReadOnlyList<Guid>> QueryIdsAsync(ISchemaEntity schema, FilterNode<ClrValue> filterNode)
{ {
var filter = filterNode.AdjustToModel(schema.SchemaDef, true).ToFilter(schema.Id); var filter = filterNode.AdjustToModel(schema.SchemaDef, true).ToFilter(schema.Id);

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

@ -65,7 +65,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
return contents.InitializeAsync(ct); return contents.InitializeAsync(ct);
} }
public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, bool inDraft, Query query, bool includeDraft = true) public async Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, bool inDraft, ClrQuery query, bool includeDraft = true)
{ {
Guard.NotNull(app, nameof(app)); Guard.NotNull(app, nameof(app));
Guard.NotNull(schema, nameof(schema)); Guard.NotNull(schema, nameof(schema));
@ -118,7 +118,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
} }
} }
public async Task<IReadOnlyList<Guid>> QueryIdsAsync(Guid appId, Guid schemaId, FilterNode filterNode) public async Task<IReadOnlyList<Guid>> QueryIdsAsync(Guid appId, Guid schemaId, FilterNode<ClrValue> filterNode)
{ {
using (Profiler.TraceMethod<MongoContentRepository>()) using (Profiler.TraceMethod<MongoContentRepository>())
{ {

24
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/AdaptionVisitor.cs

@ -13,35 +13,35 @@ using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
{ {
internal sealed class AdaptionVisitor : TransformVisitor internal sealed class AdaptionVisitor : TransformVisitor<ClrValue>
{ {
private readonly Func<IReadOnlyList<string>, IReadOnlyList<string>> pathConverter; private readonly Func<PropertyPath, PropertyPath> pathConverter;
public AdaptionVisitor(Func<IReadOnlyList<string>, IReadOnlyList<string>> pathConverter) public AdaptionVisitor(Func<PropertyPath, PropertyPath> pathConverter)
{ {
this.pathConverter = pathConverter; this.pathConverter = pathConverter;
} }
public override FilterNode Visit(FilterComparison nodeIn) public override FilterNode<ClrValue> Visit(CompareFilter<ClrValue> nodeIn)
{ {
FilterComparison result; CompareFilter<ClrValue> result;
var value = nodeIn.Rhs.Value; var value = nodeIn.Value.Value;
if (value is Instant && if (value is Instant &&
!string.Equals(nodeIn.Lhs[0], "mt", StringComparison.OrdinalIgnoreCase) && !string.Equals(nodeIn.Path[0], "mt", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(nodeIn.Lhs[0], "ct", StringComparison.OrdinalIgnoreCase)) !string.Equals(nodeIn.Path[0], "ct", StringComparison.OrdinalIgnoreCase))
{ {
result = new FilterComparison(pathConverter(nodeIn.Lhs), nodeIn.Operator, new FilterValue(value.ToString())); result = new CompareFilter<ClrValue>(pathConverter(nodeIn.Path), nodeIn.Operator, value.ToString());
} }
else else
{ {
result = new FilterComparison(pathConverter(nodeIn.Lhs), nodeIn.Operator, nodeIn.Rhs); result = new CompareFilter<ClrValue>(pathConverter(nodeIn.Path), nodeIn.Operator, nodeIn.Value);
} }
if (result.Lhs.Count == 1 && result.Lhs[0] == "_id" && result.Rhs.Value is List<Guid> guidList) if (result.Path.Count == 1 && result.Path[0] == "_id" && result.Value.Value is List<Guid> guidList)
{ {
result = new FilterComparison(nodeIn.Lhs, nodeIn.Operator, new FilterValue(guidList.Select(x => x.ToString()).ToList())); result = new CompareFilter<ClrValue>(nodeIn.Path, nodeIn.Operator, guidList.Select(x => x.ToString()).ToList());
} }
return result; return result;

19
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FilterFactory.cs

@ -28,7 +28,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
typeof(MongoContentEntity).GetProperties() typeof(MongoContentEntity).GetProperties()
.ToDictionary(x => x.Name, x => x.GetCustomAttribute<BsonElementAttribute>()?.ElementName ?? x.Name, StringComparer.OrdinalIgnoreCase); .ToDictionary(x => x.Name, x => x.GetCustomAttribute<BsonElementAttribute>()?.ElementName ?? x.Name, StringComparer.OrdinalIgnoreCase);
public static Query AdjustToModel(this Query query, Schema schema, bool useDraft) public static ClrQuery AdjustToModel(this ClrQuery query, Schema schema, bool useDraft)
{ {
var pathConverter = PathConverter(schema, useDraft); var pathConverter = PathConverter(schema, useDraft);
@ -42,14 +42,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
return query; return query;
} }
public static FilterNode AdjustToModel(this FilterNode filterNode, Schema schema, bool inDraft) public static FilterNode<ClrValue> AdjustToModel(this FilterNode<ClrValue> filterNode, Schema schema, bool inDraft)
{ {
var pathConverter = PathConverter(schema, inDraft); var pathConverter = PathConverter(schema, inDraft);
return filterNode.Accept(new AdaptionVisitor(pathConverter)); return filterNode.Accept(new AdaptionVisitor(pathConverter));
} }
private static Func<IReadOnlyList<string>, IReadOnlyList<string>> PathConverter(Schema schema, bool inDraft) private static Func<PropertyPath, PropertyPath> PathConverter(Schema schema, bool inDraft)
{ {
return propertyNames => return propertyNames =>
{ {
@ -107,17 +107,17 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
}; };
} }
public static IFindFluent<MongoContentEntity, MongoContentEntity> ContentSort(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, Query query) public static IFindFluent<MongoContentEntity, MongoContentEntity> ContentSort(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, ClrQuery query)
{ {
return cursor.Sort(query.BuildSort<MongoContentEntity>()); return cursor.Sort(query.BuildSort<MongoContentEntity>());
} }
public static IFindFluent<MongoContentEntity, MongoContentEntity> ContentTake(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, Query query) public static IFindFluent<MongoContentEntity, MongoContentEntity> ContentTake(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, ClrQuery query)
{ {
return cursor.Take(query); return cursor.Take(query);
} }
public static IFindFluent<MongoContentEntity, MongoContentEntity> ContentSkip(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, Query query) public static IFindFluent<MongoContentEntity, MongoContentEntity> ContentSkip(this IFindFluent<MongoContentEntity, MongoContentEntity> cursor, ClrQuery query)
{ {
return cursor.Skip(query); return cursor.Skip(query);
} }
@ -142,12 +142,13 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
return CreateFilter(null, schemaId, ids, status, null); return CreateFilter(null, schemaId, ids, status, null);
} }
public static FilterDefinition<MongoContentEntity> ToFilter(this Query query, Guid schemaId, ICollection<Guid> ids, Status[] status) public static FilterDefinition<MongoContentEntity> ToFilter(this ClrQuery query, Guid schemaId, ICollection<Guid> ids, Status[] status)
{ {
return CreateFilter(null, schemaId, ids, status, query); return CreateFilter(null, schemaId, ids, status, query);
} }
private static FilterDefinition<MongoContentEntity> CreateFilter(Guid? appId, Guid? schemaId, ICollection<Guid> ids, Status[] status, Query query) private static FilterDefinition<MongoContentEntity> CreateFilter(Guid? appId, Guid? schemaId, ICollection<Guid> ids, Status[] status,
ClrQuery query)
{ {
var filters = new List<FilterDefinition<MongoContentEntity>>(); var filters = new List<FilterDefinition<MongoContentEntity>>();
@ -188,7 +189,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
return Filter.And(filters); return Filter.And(filters);
} }
public static FilterDefinition<MongoContentEntity> ToFilter(this FilterNode filterNode, Guid schemaId) public static FilterDefinition<MongoContentEntity> ToFilter(this FilterNode<ClrValue> filterNode, Guid schemaId)
{ {
var filters = new List<FilterDefinition<MongoContentEntity>> var filters = new List<FilterDefinition<MongoContentEntity>>
{ {

2
src/Squidex.Domain.Apps.Entities/Assets/AssetQueryService.cs

@ -110,7 +110,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
return assets.SortSet(x => x.Id, ids); return assets.SortSet(x => x.Id, ids);
} }
private Query ParseQuery(Context context, string query) private ClrQuery ParseQuery(Context context, string query)
{ {
try try
{ {

10
src/Squidex.Domain.Apps.Entities/Assets/Queries/FilterTagTransformer.cs

@ -13,7 +13,7 @@ using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Entities.Assets.Queries namespace Squidex.Domain.Apps.Entities.Assets.Queries
{ {
public sealed class FilterTagTransformer : TransformVisitor public sealed class FilterTagTransformer : TransformVisitor<ClrValue>
{ {
private readonly ITagService tagService; private readonly ITagService tagService;
private readonly Guid appId; private readonly Guid appId;
@ -25,22 +25,22 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
this.tagService = tagService; this.tagService = tagService;
} }
public static FilterNode Transform(FilterNode nodeIn, Guid appId, ITagService tagService) public static FilterNode<ClrValue> Transform(FilterNode<ClrValue> nodeIn, Guid appId, ITagService tagService)
{ {
Guard.NotNull(tagService, nameof(tagService)); Guard.NotNull(tagService, nameof(tagService));
return nodeIn.Accept(new FilterTagTransformer(appId, tagService)); return nodeIn.Accept(new FilterTagTransformer(appId, tagService));
} }
public override FilterNode Visit(FilterComparison nodeIn) public override FilterNode<ClrValue> Visit(CompareFilter<ClrValue> nodeIn)
{ {
if (string.Equals(nodeIn.Lhs[0], nameof(IAssetEntity.Tags), StringComparison.OrdinalIgnoreCase) && nodeIn.Rhs.Value is string stringValue) if (string.Equals(nodeIn.Path[0], nameof(IAssetEntity.Tags), StringComparison.OrdinalIgnoreCase) && nodeIn.Value.Value is string stringValue)
{ {
var tagNames = Task.Run(() => tagService.GetTagIdsAsync(appId, TagGroups.Assets, HashSet.Of(stringValue))).Result; var tagNames = Task.Run(() => tagService.GetTagIdsAsync(appId, TagGroups.Assets, HashSet.Of(stringValue))).Result;
if (tagNames.TryGetValue(stringValue, out var normalized)) if (tagNames.TryGetValue(stringValue, out var normalized))
{ {
return new FilterComparison(nodeIn.Lhs, nodeIn.Operator, new FilterValue(normalized)); return new CompareFilter<ClrValue>(nodeIn.Path, nodeIn.Operator, normalized);
} }
} }

2
src/Squidex.Domain.Apps.Entities/Assets/Repositories/IAssetRepository.cs

@ -17,7 +17,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Repositories
{ {
Task<IReadOnlyList<IAssetEntity>> QueryByHashAsync(Guid appId, string hash); Task<IReadOnlyList<IAssetEntity>> QueryByHashAsync(Guid appId, string hash);
Task<IResultList<IAssetEntity>> QueryAsync(Guid appId, Query query); Task<IResultList<IAssetEntity>> QueryAsync(Guid appId, ClrQuery query);
Task<IResultList<IAssetEntity>> QueryAsync(Guid appId, HashSet<Guid> ids); Task<IResultList<IAssetEntity>> QueryAsync(Guid appId, HashSet<Guid> ids);

2
src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs

@ -122,7 +122,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
return await assetRepository.QueryAsync(appEntity.Id, new HashSet<Guid>(assetIds)); return await assetRepository.QueryAsync(appEntity.Id, new HashSet<Guid>(assetIds));
} }
private async Task<IReadOnlyList<Guid>> QueryContentsAsync(Guid filterSchemaId, FilterNode filterNode) private async Task<IReadOnlyList<Guid>> QueryContentsAsync(Guid filterSchemaId, FilterNode<ClrValue> filterNode)
{ {
return await contentRepository.QueryIdsAsync(appEntity.Id, filterSchemaId, filterNode); return await contentRepository.QueryIdsAsync(appEntity.Id, filterSchemaId, filterNode);
} }

4
src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs

@ -254,7 +254,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
} }
} }
private Query ParseQuery(Context context, string query, ISchemaEntity schema) private ClrQuery ParseQuery(Context context, string query, ISchemaEntity schema)
{ {
using (Profiler.TraceMethod<ContentQueryService>()) using (Profiler.TraceMethod<ContentQueryService>())
{ {
@ -362,7 +362,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
return contentRepository.QueryAsync(context.App, GetStatus(context), new HashSet<Guid>(ids), WithDraft(context)); return contentRepository.QueryAsync(context.App, GetStatus(context), new HashSet<Guid>(ids), WithDraft(context));
} }
private Task<IResultList<IContentEntity>> QueryCoreAsync(Context context, ISchemaEntity schema, Query query) private Task<IResultList<IContentEntity>> QueryCoreAsync(Context context, ISchemaEntity schema, ClrQuery query)
{ {
return contentRepository.QueryAsync(context.App, schema, GetStatus(context), context.IsFrontendClient, query, WithDraft(context)); return contentRepository.QueryAsync(context.App, schema, GetStatus(context), context.IsFrontendClient, query, WithDraft(context));
} }

10
src/Squidex.Domain.Apps.Entities/Contents/Queries/FilterTagTransformer.cs

@ -16,7 +16,7 @@ using Squidex.Infrastructure.Queries;
namespace Squidex.Domain.Apps.Entities.Contents.Queries namespace Squidex.Domain.Apps.Entities.Contents.Queries
{ {
public sealed class FilterTagTransformer : TransformVisitor public sealed class FilterTagTransformer : TransformVisitor<ClrValue>
{ {
private readonly ITagService tagService; private readonly ITagService tagService;
private readonly ISchemaEntity schema; private readonly ISchemaEntity schema;
@ -29,7 +29,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
this.tagService = tagService; this.tagService = tagService;
} }
public static FilterNode Transform(FilterNode nodeIn, Guid appId, ISchemaEntity schema, ITagService tagService) public static FilterNode<ClrValue> Transform(FilterNode<ClrValue> nodeIn, Guid appId, ISchemaEntity schema, ITagService tagService)
{ {
Guard.NotNull(tagService, nameof(tagService)); Guard.NotNull(tagService, nameof(tagService));
Guard.NotNull(schema, nameof(schema)); Guard.NotNull(schema, nameof(schema));
@ -37,15 +37,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
return nodeIn.Accept(new FilterTagTransformer(appId, schema, tagService)); return nodeIn.Accept(new FilterTagTransformer(appId, schema, tagService));
} }
public override FilterNode Visit(FilterComparison nodeIn) public override FilterNode<ClrValue> Visit(CompareFilter<ClrValue> nodeIn)
{ {
if (nodeIn.Rhs.Value is string stringValue && IsDataPath(nodeIn.Lhs) && IsTagField(nodeIn.Lhs)) if (nodeIn.Value.Value is string stringValue && IsDataPath(nodeIn.Path) && IsTagField(nodeIn.Path))
{ {
var tagNames = Task.Run(() => tagService.GetTagIdsAsync(appId, TagGroups.Schemas(schema.Id), HashSet.Of(stringValue))).Result; var tagNames = Task.Run(() => tagService.GetTagIdsAsync(appId, TagGroups.Schemas(schema.Id), HashSet.Of(stringValue))).Result;
if (tagNames.TryGetValue(stringValue, out var normalized)) if (tagNames.TryGetValue(stringValue, out var normalized))
{ {
return new FilterComparison(nodeIn.Lhs, nodeIn.Operator, new FilterValue(normalized)); return new CompareFilter<ClrValue>(nodeIn.Path, nodeIn.Operator, normalized);
} }
} }

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

@ -23,9 +23,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.Repositories
Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids, bool includeDraft); Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, HashSet<Guid> ids, bool includeDraft);
Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, bool inDraft, Query query, bool includeDraft); Task<IResultList<IContentEntity>> QueryAsync(IAppEntity app, ISchemaEntity schema, Status[] status, bool inDraft, ClrQuery query, bool includeDraft);
Task<IReadOnlyList<Guid>> QueryIdsAsync(Guid appId, Guid schemaId, FilterNode filterNode); Task<IReadOnlyList<Guid>> QueryIdsAsync(Guid appId, Guid schemaId, FilterNode<ClrValue> filterNode);
Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Guid id, bool includeDraft); Task<IContentEntity> FindContentAsync(IAppEntity app, ISchemaEntity schema, Status[] status, Guid id, bool includeDraft);

4
src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterBuilder.cs

@ -12,7 +12,7 @@ namespace Squidex.Infrastructure.MongoDb.Queries
{ {
public static class FilterBuilder public static class FilterBuilder
{ {
public static (FilterDefinition<T> Filter, bool Last) BuildFilter<T>(this Query query, bool supportsSearch = true) public static (FilterDefinition<T> Filter, bool Last) BuildFilter<T>(this ClrQuery query, bool supportsSearch = true)
{ {
if (query.FullText != null) if (query.FullText != null)
{ {
@ -32,7 +32,7 @@ namespace Squidex.Infrastructure.MongoDb.Queries
return (null, false); return (null, false);
} }
public static FilterDefinition<T> BuildFilter<T>(this FilterNode filterNode) public static FilterDefinition<T> BuildFilter<T>(this FilterNode<ClrValue> filterNode)
{ {
return FilterVisitor<T>.Visit(filterNode); return FilterVisitor<T>.Visit(filterNode);
} }

60
src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterVisitor.cs

@ -14,7 +14,7 @@ using Squidex.Infrastructure.Queries;
namespace Squidex.Infrastructure.MongoDb.Queries namespace Squidex.Infrastructure.MongoDb.Queries
{ {
public sealed class FilterVisitor<T> : FilterNodeVisitor<FilterDefinition<T>> public sealed class FilterVisitor<T> : FilterNodeVisitor<FilterDefinition<T>, ClrValue>
{ {
private static readonly FilterDefinitionBuilder<T> Filter = Builders<T>.Filter; private static readonly FilterDefinitionBuilder<T> Filter = Builders<T>.Filter;
private static readonly FilterVisitor<T> Instance = new FilterVisitor<T>(); private static readonly FilterVisitor<T> Instance = new FilterVisitor<T>();
@ -23,64 +23,64 @@ namespace Squidex.Infrastructure.MongoDb.Queries
{ {
} }
public static FilterDefinition<T> Visit(FilterNode node) public static FilterDefinition<T> Visit(FilterNode<ClrValue> node)
{ {
return node.Accept(Instance); return node.Accept(Instance);
} }
public override FilterDefinition<T> Visit(FilterNegate nodeIn) public override FilterDefinition<T> Visit(NegateFilter<ClrValue> nodeIn)
{ {
return Filter.Not(nodeIn.Operand.Accept(this)); return Filter.Not(nodeIn.Filter.Accept(this));
} }
public override FilterDefinition<T> Visit(FilterJunction nodeIn) public override FilterDefinition<T> Visit(LogicalFilter<ClrValue> nodeIn)
{ {
if (nodeIn.JunctionType == FilterJunctionType.And) if (nodeIn.Type == LogicalFilterType.And)
{ {
return Filter.And(nodeIn.Operands.Select(x => x.Accept(this))); return Filter.And(nodeIn.Filters.Select(x => x.Accept(this)));
} }
else else
{ {
return Filter.Or(nodeIn.Operands.Select(x => x.Accept(this))); return Filter.Or(nodeIn.Filters.Select(x => x.Accept(this)));
} }
} }
public override FilterDefinition<T> Visit(FilterComparison nodeIn) public override FilterDefinition<T> Visit(CompareFilter<ClrValue> nodeIn)
{ {
var propertyName = string.Join(".", nodeIn.Lhs); var propertyName = nodeIn.Path.ToString();
switch (nodeIn.Operator) switch (nodeIn.Operator)
{ {
case FilterOperator.Empty: case CompareOperator.Empty:
return Filter.Or(Filter.Exists(propertyName, false), Filter.Eq(propertyName, default(T)), Filter.Eq(propertyName, string.Empty), Filter.Eq(propertyName, new T[0])); return Filter.Or(Filter.Exists(propertyName, false), Filter.Eq(propertyName, default(T)), Filter.Eq(propertyName, string.Empty), Filter.Eq(propertyName, new T[0]));
case FilterOperator.StartsWith: case CompareOperator.StartsWith:
return Filter.Regex(propertyName, BuildRegex(nodeIn, s => "^" + s)); return Filter.Regex(propertyName, BuildRegex(nodeIn, s => "^" + s));
case FilterOperator.Contains: case CompareOperator.Contains:
return Filter.Regex(propertyName, BuildRegex(nodeIn, s => s)); return Filter.Regex(propertyName, BuildRegex(nodeIn, s => s));
case FilterOperator.EndsWith: case CompareOperator.EndsWith:
return Filter.Regex(propertyName, BuildRegex(nodeIn, s => s + "$")); return Filter.Regex(propertyName, BuildRegex(nodeIn, s => s + "$"));
case FilterOperator.Equals: case CompareOperator.Equals:
return Filter.Eq(propertyName, nodeIn.Rhs.Value); return Filter.Eq(propertyName, nodeIn.Value.Value);
case FilterOperator.GreaterThan: case CompareOperator.GreaterThan:
return Filter.Gt(propertyName, nodeIn.Rhs.Value); return Filter.Gt(propertyName, nodeIn.Value.Value);
case FilterOperator.GreaterThanOrEqual: case CompareOperator.GreaterThanOrEqual:
return Filter.Gte(propertyName, nodeIn.Rhs.Value); return Filter.Gte(propertyName, nodeIn.Value.Value);
case FilterOperator.LessThan: case CompareOperator.LessThan:
return Filter.Lt(propertyName, nodeIn.Rhs.Value); return Filter.Lt(propertyName, nodeIn.Value.Value);
case FilterOperator.LessThanOrEqual: case CompareOperator.LessThanOrEqual:
return Filter.Lte(propertyName, nodeIn.Rhs.Value); return Filter.Lte(propertyName, nodeIn.Value.Value);
case FilterOperator.NotEquals: case CompareOperator.NotEquals:
return Filter.Ne(propertyName, nodeIn.Rhs.Value); return Filter.Ne(propertyName, nodeIn.Value.Value);
case FilterOperator.In: case CompareOperator.In:
return Filter.In(propertyName, ((IList)nodeIn.Rhs.Value).OfType<object>()); return Filter.In(propertyName, ((IList)nodeIn.Value.Value).OfType<object>());
} }
throw new NotSupportedException(); throw new NotSupportedException();
} }
private static BsonRegularExpression BuildRegex(FilterComparison node, Func<string, string> formatter) private static BsonRegularExpression BuildRegex(CompareFilter<ClrValue> node, Func<string, string> formatter)
{ {
return new BsonRegularExpression(formatter(node.Rhs.Value.ToString()), "i"); return new BsonRegularExpression(formatter(node.Value.Value.ToString()), "i");
} }
} }
} }

4
src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/LimitExtensions.cs

@ -12,7 +12,7 @@ namespace Squidex.Infrastructure.MongoDb.Queries
{ {
public static class LimitExtensions public static class LimitExtensions
{ {
public static IFindFluent<T, T> Take<T>(this IFindFluent<T, T> cursor, Query query) public static IFindFluent<T, T> Take<T>(this IFindFluent<T, T> cursor, ClrQuery query)
{ {
if (query.Take < long.MaxValue) if (query.Take < long.MaxValue)
{ {
@ -22,7 +22,7 @@ namespace Squidex.Infrastructure.MongoDb.Queries
return cursor; return cursor;
} }
public static IFindFluent<T, T> Skip<T>(this IFindFluent<T, T> cursor, Query query) public static IFindFluent<T, T> Skip<T>(this IFindFluent<T, T> cursor, ClrQuery query)
{ {
if (query.Skip > 0) if (query.Skip > 0)
{ {

2
src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/SortBuilder.cs

@ -13,7 +13,7 @@ namespace Squidex.Infrastructure.MongoDb.Queries
{ {
public static class SortBuilder public static class SortBuilder
{ {
public static SortDefinition<T> BuildSort<T>(this Query query) public static SortDefinition<T> BuildSort<T>(this ClrQuery query)
{ {
if (query.Sort.Count > 0) if (query.Sort.Count > 0)
{ {

87
src/Squidex.Infrastructure/Queries/ClrFilter.cs

@ -0,0 +1,87 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.Queries
{
public static class ClrFilter
{
public static LogicalFilter<ClrValue> And(params FilterNode<ClrValue>[] filters)
{
return new LogicalFilter<ClrValue>(LogicalFilterType.And, filters);
}
public static LogicalFilter<ClrValue> Or(params FilterNode<ClrValue>[] filters)
{
return new LogicalFilter<ClrValue>(LogicalFilterType.Or, filters);
}
public static NegateFilter<ClrValue> Not(FilterNode<ClrValue> filter)
{
return new NegateFilter<ClrValue>(filter);
}
public static CompareFilter<ClrValue> Eq(PropertyPath path, ClrValue value)
{
return Binary(path, CompareOperator.Equals, value);
}
public static CompareFilter<ClrValue> Ne(PropertyPath path, ClrValue value)
{
return Binary(path, CompareOperator.NotEquals, value);
}
public static CompareFilter<ClrValue> Lt(PropertyPath path, ClrValue value)
{
return Binary(path, CompareOperator.LessThan, value);
}
public static CompareFilter<ClrValue> Le(PropertyPath path, ClrValue value)
{
return Binary(path, CompareOperator.LessThanOrEqual, value);
}
public static CompareFilter<ClrValue> Gt(PropertyPath path, ClrValue value)
{
return Binary(path, CompareOperator.GreaterThan, value);
}
public static CompareFilter<ClrValue> Ge(PropertyPath path, ClrValue value)
{
return Binary(path, CompareOperator.GreaterThanOrEqual, value);
}
public static CompareFilter<ClrValue> Contains(PropertyPath path, ClrValue value)
{
return Binary(path, CompareOperator.Contains, value);
}
public static CompareFilter<ClrValue> EndsWith(PropertyPath path, ClrValue value)
{
return Binary(path, CompareOperator.EndsWith, value);
}
public static CompareFilter<ClrValue> StartsWith(PropertyPath path, ClrValue value)
{
return Binary(path, CompareOperator.StartsWith, value);
}
public static CompareFilter<ClrValue> Empty(PropertyPath path)
{
return Binary(path, CompareOperator.Empty, null);
}
public static CompareFilter<ClrValue> In(PropertyPath path, ClrValue value)
{
return Binary(path, CompareOperator.In, value);
}
private static CompareFilter<ClrValue> Binary(PropertyPath path, CompareOperator @operator, ClrValue value)
{
return new CompareFilter<ClrValue>(path, @operator, value ?? ClrValue.Null);
}
}
}

13
src/Squidex.Infrastructure/Queries/ClrQuery.cs

@ -0,0 +1,13 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.Queries
{
public sealed class ClrQuery : Query<ClrValue>
{
}
}

140
src/Squidex.Infrastructure/Queries/ClrValue.cs

@ -0,0 +1,140 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using NodaTime;
namespace Squidex.Infrastructure.Queries
{
public sealed class ClrValue
{
public static readonly ClrValue Null = new ClrValue(null, ClrValueType.Null, false);
public object Value { get; }
public ClrValueType ValueType { get; }
public bool IsList { get; }
private ClrValue(object value, ClrValueType valueType, bool isList)
{
Value = value;
ValueType = valueType;
IsList = isList;
}
public static implicit operator ClrValue(Instant value)
{
return new ClrValue(value, ClrValueType.Instant, false);
}
public static implicit operator ClrValue(Guid value)
{
return new ClrValue(value, ClrValueType.Guid, false);
}
public static implicit operator ClrValue(bool value)
{
return new ClrValue(value, ClrValueType.Boolean, false);
}
public static implicit operator ClrValue(float value)
{
return new ClrValue(value, ClrValueType.Single, false);
}
public static implicit operator ClrValue(double value)
{
return new ClrValue(value, ClrValueType.Double, false);
}
public static implicit operator ClrValue(int value)
{
return new ClrValue(value, ClrValueType.Int32, false);
}
public static implicit operator ClrValue(long value)
{
return new ClrValue(value, ClrValueType.Int64, false);
}
public static implicit operator ClrValue(string value)
{
return value != null ? new ClrValue(value, ClrValueType.String, false) : Null;
}
public static implicit operator ClrValue(List<Instant> value)
{
return value != null ? new ClrValue(value, ClrValueType.Instant, true) : Null;
}
public static implicit operator ClrValue(List<Guid> value)
{
return value != null ? new ClrValue(value, ClrValueType.Guid, true) : Null;
}
public static implicit operator ClrValue(List<bool> value)
{
return value != null ? new ClrValue(value, ClrValueType.Boolean, true) : Null;
}
public static implicit operator ClrValue(List<float> value)
{
return value != null ? new ClrValue(value, ClrValueType.Single, true) : Null;
}
public static implicit operator ClrValue(List<double> value)
{
return value != null ? new ClrValue(value, ClrValueType.Double, true) : Null;
}
public static implicit operator ClrValue(List<int> value)
{
return value != null ? new ClrValue(value, ClrValueType.Int32, true) : Null;
}
public static implicit operator ClrValue(List<long> value)
{
return value != null ? new ClrValue(value, ClrValueType.Int64, true) : Null;
}
public static implicit operator ClrValue(List<string> value)
{
return value != null ? new ClrValue(value, ClrValueType.String, true) : Null;
}
public override string ToString()
{
if (Value is IList list)
{
return $"[{string.Join(", ", list.OfType<object>().Select(ToString).ToArray())}]";
}
return ToString(Value);
}
private static string ToString(object value)
{
if (value == null)
{
return "null";
}
if (value is string s)
{
return $"'{s.Replace("'", "\\'")}'";
}
return string.Format(CultureInfo.InvariantCulture, "{0}", value);
}
}
}

2
src/Squidex.Infrastructure/Queries/FilterValueType.cs → src/Squidex.Infrastructure/Queries/ClrValueType.cs

@ -7,7 +7,7 @@
namespace Squidex.Infrastructure.Queries namespace Squidex.Infrastructure.Queries
{ {
public enum FilterValueType public enum ClrValueType
{ {
Boolean, Boolean,
Guid, Guid,

67
src/Squidex.Infrastructure/Queries/CompareFilter.cs

@ -0,0 +1,67 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.Queries
{
public sealed class CompareFilter<TValue> : FilterNode<TValue>
{
public PropertyPath Path { get; }
public CompareOperator Operator { get; }
public TValue Value { get; }
public CompareFilter(PropertyPath path, CompareOperator @operator, TValue value)
{
Guard.NotNull(path, nameof(path));
Guard.NotNull(value, nameof(value));
Guard.Enum(@operator, nameof(@operator));
Path = path;
Operator = @operator;
Value = value;
}
public override T Accept<T>(FilterNodeVisitor<T, TValue> visitor)
{
return visitor.Visit(this);
}
public override string ToString()
{
switch (Operator)
{
case CompareOperator.Contains:
return $"contains({Path}, {Value})";
case CompareOperator.Empty:
return $"empty({Path})";
case CompareOperator.EndsWith:
return $"endsWith({Path}, {Value})";
case CompareOperator.StartsWith:
return $"startsWith({Path}, {Value})";
case CompareOperator.Equals:
return $"{Path} == {Value}";
case CompareOperator.NotEquals:
return $"{Path} != {Value}";
case CompareOperator.GreaterThan:
return $"{Path} > {Value}";
case CompareOperator.GreaterThanOrEqual:
return $"{Path} >= {Value}";
case CompareOperator.LessThan:
return $"{Path} < {Value}";
case CompareOperator.LessThanOrEqual:
return $"{Path} <= {Value}";
case CompareOperator.In:
return $"{Path} in {Value}";
default:
return string.Empty;
}
}
}
}

2
src/Squidex.Infrastructure/Queries/FilterOperator.cs → src/Squidex.Infrastructure/Queries/CompareOperator.cs

@ -7,7 +7,7 @@
namespace Squidex.Infrastructure.Queries namespace Squidex.Infrastructure.Queries
{ {
public enum FilterOperator public enum CompareOperator
{ {
Contains, Contains,
Empty, Empty,

85
src/Squidex.Infrastructure/Queries/FilterBuilder.cs

@ -1,85 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Linq;
using NodaTime;
namespace Squidex.Infrastructure.Queries
{
public static class FilterBuilder
{
public static FilterJunction And(params FilterNode[] operands)
{
return new FilterJunction(FilterJunctionType.And, operands);
}
public static FilterJunction Or(params FilterNode[] operands)
{
return new FilterJunction(FilterJunctionType.Or, operands);
}
public static FilterComparison Eq(string path, string value)
{
return Binary(path, FilterOperator.Equals, value);
}
public static FilterComparison Eq(string path, bool value)
{
return Binary(path, FilterOperator.Equals, value);
}
public static FilterComparison Eq(string path, long value)
{
return Binary(path, FilterOperator.Equals, value);
}
public static FilterComparison Eq(string path, int value)
{
return Binary(path, FilterOperator.Equals, value);
}
public static FilterComparison Eq(string path, Instant value)
{
return Binary(path, FilterOperator.Equals, value);
}
public static FilterComparison Empty(string path)
{
return new FilterComparison(path.Split('.', '/'), FilterOperator.Empty, FilterValue.Null);
}
public static FilterComparison In(string path, params long[] value)
{
return new FilterComparison(path.Split('.', '/'), FilterOperator.In, new FilterValue(value.ToList()));
}
private static FilterComparison Binary(string path, FilterOperator @operator, string value)
{
return new FilterComparison(path.Split('.', '/'), @operator, new FilterValue(value));
}
private static FilterComparison Binary(string path, FilterOperator @operator, bool value)
{
return new FilterComparison(path.Split('.', '/'), @operator, new FilterValue(value));
}
private static FilterComparison Binary(string path, FilterOperator @operator, long value)
{
return new FilterComparison(path.Split('.', '/'), @operator, new FilterValue(value));
}
private static FilterComparison Binary(string path, FilterOperator @operator, int value)
{
return new FilterComparison(path.Split('.', '/'), @operator, new FilterValue(value));
}
private static FilterComparison Binary(string path, FilterOperator @operator, Instant value)
{
return new FilterComparison(path.Split('.', '/'), @operator, new FilterValue(value));
}
}
}

70
src/Squidex.Infrastructure/Queries/FilterComparison.cs

@ -1,70 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
namespace Squidex.Infrastructure.Queries
{
public sealed class FilterComparison : FilterNode
{
public IReadOnlyList<string> Lhs { get; }
public FilterOperator Operator { get; }
public FilterValue Rhs { get; }
public FilterComparison(IReadOnlyList<string> lhs, FilterOperator @operator, FilterValue rhs)
{
Guard.NotNull(lhs, nameof(lhs));
Guard.NotEmpty(lhs, nameof(lhs));
Guard.Enum(@operator, nameof(@operator));
Lhs = lhs;
Rhs = rhs;
Operator = @operator;
}
public override T Accept<T>(FilterNodeVisitor<T> visitor)
{
return visitor.Visit(this);
}
public override string ToString()
{
var path = string.Join(".", Lhs);
switch (Operator)
{
case FilterOperator.Contains:
return $"contains({path}, {Rhs})";
case FilterOperator.Empty:
return $"empty({path})";
case FilterOperator.EndsWith:
return $"endsWith({path}, {Rhs})";
case FilterOperator.StartsWith:
return $"startsWith({path}, {Rhs})";
case FilterOperator.Equals:
return $"{path} == {Rhs}";
case FilterOperator.NotEquals:
return $"{path} != {Rhs}";
case FilterOperator.GreaterThan:
return $"{path} > {Rhs}";
case FilterOperator.GreaterThanOrEqual:
return $"{path} >= {Rhs}";
case FilterOperator.LessThan:
return $"{path} < {Rhs}";
case FilterOperator.LessThanOrEqual:
return $"{path} <= {Rhs}";
case FilterOperator.In:
return $"{path} in {Rhs}";
default:
return string.Empty;
}
}
}
}

45
src/Squidex.Infrastructure/Queries/FilterJunction.cs

@ -1,45 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
namespace Squidex.Infrastructure.Queries
{
public sealed class FilterJunction : FilterNode
{
public IReadOnlyList<FilterNode> Operands { get; }
public FilterJunctionType JunctionType { get; }
public FilterJunction(FilterJunctionType junctionType, IReadOnlyList<FilterNode> operands)
{
Guard.NotNull(operands, nameof(operands));
Guard.GreaterEquals(operands.Count, 2, nameof(operands.Count));
Guard.Enum(junctionType, nameof(junctionType));
Operands = operands;
JunctionType = junctionType;
}
public FilterJunction(FilterJunctionType junctionType, params FilterNode[] operands)
: this(junctionType, operands?.ToList())
{
}
public override T Accept<T>(FilterNodeVisitor<T> visitor)
{
return visitor.Visit(this);
}
public override string ToString()
{
return $"({string.Join(JunctionType == FilterJunctionType.And ? " && " : " || ", Operands)})";
}
}
}

4
src/Squidex.Infrastructure/Queries/FilterNode.cs

@ -7,8 +7,8 @@
namespace Squidex.Infrastructure.Queries namespace Squidex.Infrastructure.Queries
{ {
public abstract class FilterNode public abstract class FilterNode<TValue>
{ {
public abstract T Accept<T>(FilterNodeVisitor<T> visitor); public abstract T Accept<T>(FilterNodeVisitor<T, TValue> visitor);
} }
} }

8
src/Squidex.Infrastructure/Queries/FilterNodeVisitor.cs

@ -11,19 +11,19 @@ using System;
namespace Squidex.Infrastructure.Queries namespace Squidex.Infrastructure.Queries
{ {
public abstract class FilterNodeVisitor<T> public abstract class FilterNodeVisitor<T, TValue>
{ {
public virtual T Visit(FilterComparison nodeIn) public virtual T Visit(CompareFilter<TValue> nodeIn)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public virtual T Visit(FilterJunction nodeIn) public virtual T Visit(LogicalFilter<TValue> nodeIn)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public virtual T Visit(FilterNegate nodeIn) public virtual T Visit(NegateFilter<TValue> nodeIn)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

149
src/Squidex.Infrastructure/Queries/FilterValue.cs

@ -1,149 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using NodaTime;
namespace Squidex.Infrastructure.Queries
{
public sealed class FilterValue
{
public static readonly FilterValue Null = new FilterValue(null, FilterValueType.Null, false);
public object Value { get; }
public FilterValueType ValueType { get; }
public bool IsList { get; }
public FilterValue(Guid value)
: this(value, FilterValueType.Guid, false)
{
}
public FilterValue(Instant value)
: this(value, FilterValueType.Instant, false)
{
}
public FilterValue(bool value)
: this(value, FilterValueType.Boolean, false)
{
}
public FilterValue(float value)
: this(value, FilterValueType.Single, false)
{
}
public FilterValue(double value)
: this(value, FilterValueType.Double, false)
{
}
public FilterValue(int value)
: this(value, FilterValueType.Int32, false)
{
}
public FilterValue(long value)
: this(value, FilterValueType.Int64, false)
{
}
public FilterValue(string value)
: this(value, FilterValueType.String, false)
{
Guard.NotNull(value, nameof(value));
}
public FilterValue(List<Guid> value)
: this(value, FilterValueType.Guid, true)
{
Guard.NotNull(value, nameof(value));
}
public FilterValue(List<Instant> value)
: this(value, FilterValueType.Instant, true)
{
Guard.NotNull(value, nameof(value));
}
public FilterValue(List<bool> value)
: this(value, FilterValueType.Boolean, true)
{
Guard.NotNull(value, nameof(value));
}
public FilterValue(List<float> value)
: this(value, FilterValueType.Single, true)
{
Guard.NotNull(value, nameof(value));
}
public FilterValue(List<double> value)
: this(value, FilterValueType.Double, true)
{
Guard.NotNull(value, nameof(value));
}
public FilterValue(List<int> value)
: this(value, FilterValueType.Int32, true)
{
Guard.NotNull(value, nameof(value));
}
public FilterValue(List<long> value)
: this(value, FilterValueType.Int64, true)
{
Guard.NotNull(value, nameof(value));
}
public FilterValue(List<string> value)
: this(value, FilterValueType.String, true)
{
Guard.NotNull(value, nameof(value));
}
private FilterValue(object value, FilterValueType valueType, bool isList)
{
Value = value;
ValueType = valueType;
IsList = isList;
}
public override string ToString()
{
if (Value is IList list)
{
return $"[{string.Join(", ", list.OfType<object>().Select(ToString).ToArray())}]";
}
return ToString(Value);
}
private static string ToString(object value)
{
if (value == null)
{
return "null";
}
if (value is string s)
{
return $"'{s.Replace("'", "\\'")}'";
}
return string.Format(CultureInfo.InvariantCulture, "{0}", value);
}
}
}

39
src/Squidex.Infrastructure/Queries/LogicalFilter.cs

@ -0,0 +1,39 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
namespace Squidex.Infrastructure.Queries
{
public sealed class LogicalFilter<TValue> : FilterNode<TValue>
{
public IReadOnlyList<FilterNode<TValue>> Filters { get; }
public LogicalFilterType Type { get; }
public LogicalFilter(LogicalFilterType type, IReadOnlyList<FilterNode<TValue>> filters)
{
Guard.NotNull(filters, nameof(filters));
Guard.GreaterEquals(filters.Count, 2, nameof(filters.Count));
Guard.Enum(type, nameof(type));
Filters = filters;
Type = type;
}
public override T Accept<T>(FilterNodeVisitor<T, TValue> visitor)
{
return visitor.Visit(this);
}
public override string ToString()
{
return $"({string.Join(Type == LogicalFilterType.And ? " && " : " || ", Filters)})";
}
}
}

2
src/Squidex.Infrastructure/Queries/FilterJunctionType.cs → src/Squidex.Infrastructure/Queries/LogicalFilterType.cs

@ -7,7 +7,7 @@
namespace Squidex.Infrastructure.Queries namespace Squidex.Infrastructure.Queries
{ {
public enum FilterJunctionType public enum LogicalFilterType
{ {
And, And,
Or Or

14
src/Squidex.Infrastructure/Queries/FilterNegate.cs → src/Squidex.Infrastructure/Queries/NegateFilter.cs

@ -7,25 +7,25 @@
namespace Squidex.Infrastructure.Queries namespace Squidex.Infrastructure.Queries
{ {
public sealed class FilterNegate : FilterNode public sealed class NegateFilter<TValue> : FilterNode<TValue>
{ {
public FilterNode Operand { get; } public FilterNode<TValue> Filter { get; }
public FilterNegate(FilterNode operand) public NegateFilter(FilterNode<TValue> filter)
{ {
Guard.NotNull(operand, nameof(operand)); Guard.NotNull(filter, nameof(filter));
Operand = operand; Filter = filter;
} }
public override T Accept<T>(FilterNodeVisitor<T> visitor) public override T Accept<T>(FilterNodeVisitor<T, TValue> visitor)
{ {
return visitor.Visit(this); return visitor.Visit(this);
} }
public override string ToString() public override string ToString()
{ {
return $"!({Operand})"; return $"!({Filter})";
} }
} }
} }

46
src/Squidex.Infrastructure/Queries/OData/ConstantWithTypeVisitor.cs

@ -15,7 +15,7 @@ using NodaTime.Text;
namespace Squidex.Infrastructure.Queries.OData namespace Squidex.Infrastructure.Queries.OData
{ {
public sealed class ConstantWithTypeVisitor : QueryNodeVisitor<FilterValue> public sealed class ConstantWithTypeVisitor : QueryNodeVisitor<ClrValue>
{ {
private static readonly IEdmPrimitiveType BooleanType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Boolean); private static readonly IEdmPrimitiveType BooleanType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Boolean);
private static readonly IEdmPrimitiveType DateTimeType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.DateTimeOffset); private static readonly IEdmPrimitiveType DateTimeType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.DateTimeOffset);
@ -32,117 +32,117 @@ namespace Squidex.Infrastructure.Queries.OData
{ {
} }
public static FilterValue Visit(QueryNode node) public static ClrValue Visit(QueryNode node)
{ {
return node.Accept(Instance); return node.Accept(Instance);
} }
public override FilterValue Visit(ConvertNode nodeIn) public override ClrValue Visit(ConvertNode nodeIn)
{ {
if (nodeIn.TypeReference.Definition == BooleanType) if (nodeIn.TypeReference.Definition == BooleanType)
{ {
var value = ConstantVisitor.Visit(nodeIn.Source); var value = ConstantVisitor.Visit(nodeIn.Source);
return new FilterValue(bool.Parse(value.ToString())); return bool.Parse(value.ToString());
} }
if (nodeIn.TypeReference.Definition == GuidType) if (nodeIn.TypeReference.Definition == GuidType)
{ {
var value = ConstantVisitor.Visit(nodeIn.Source); var value = ConstantVisitor.Visit(nodeIn.Source);
return new FilterValue(Guid.Parse(value.ToString())); return Guid.Parse(value.ToString());
} }
if (nodeIn.TypeReference.Definition == DateTimeType) if (nodeIn.TypeReference.Definition == DateTimeType)
{ {
var value = ConstantVisitor.Visit(nodeIn.Source); var value = ConstantVisitor.Visit(nodeIn.Source);
return new FilterValue(ParseInstant(value)); return ParseInstant(value);
} }
if (ConstantVisitor.Visit(nodeIn.Source) == null) if (ConstantVisitor.Visit(nodeIn.Source) == null)
{ {
return FilterValue.Null; return ClrValue.Null;
} }
throw new NotSupportedException(); throw new NotSupportedException();
} }
public override FilterValue Visit(CollectionConstantNode nodeIn) public override ClrValue Visit(CollectionConstantNode nodeIn)
{ {
if (nodeIn.ItemType.Definition == DateTimeType) if (nodeIn.ItemType.Definition == DateTimeType)
{ {
return new FilterValue(nodeIn.Collection.Select(x => ParseInstant(x.Value)).ToList()); return nodeIn.Collection.Select(x => ParseInstant(x.Value)).ToList();
} }
if (nodeIn.ItemType.Definition == GuidType) if (nodeIn.ItemType.Definition == GuidType)
{ {
return new FilterValue(nodeIn.Collection.Select(x => (Guid)x.Value).ToList()); return nodeIn.Collection.Select(x => (Guid)x.Value).ToList();
} }
if (nodeIn.ItemType.Definition == BooleanType) if (nodeIn.ItemType.Definition == BooleanType)
{ {
return new FilterValue(nodeIn.Collection.Select(x => (bool)x.Value).ToList()); return nodeIn.Collection.Select(x => (bool)x.Value).ToList();
} }
if (nodeIn.ItemType.Definition == SingleType) if (nodeIn.ItemType.Definition == SingleType)
{ {
return new FilterValue(nodeIn.Collection.Select(x => (float)x.Value).ToList()); return nodeIn.Collection.Select(x => (float)x.Value).ToList();
} }
if (nodeIn.ItemType.Definition == DoubleType) if (nodeIn.ItemType.Definition == DoubleType)
{ {
return new FilterValue(nodeIn.Collection.Select(x => (double)x.Value).ToList()); return nodeIn.Collection.Select(x => (double)x.Value).ToList();
} }
if (nodeIn.ItemType.Definition == Int32Type) if (nodeIn.ItemType.Definition == Int32Type)
{ {
return new FilterValue(nodeIn.Collection.Select(x => (int)x.Value).ToList()); return nodeIn.Collection.Select(x => (int)x.Value).ToList();
} }
if (nodeIn.ItemType.Definition == Int64Type) if (nodeIn.ItemType.Definition == Int64Type)
{ {
return new FilterValue(nodeIn.Collection.Select(x => (long)x.Value).ToList()); return nodeIn.Collection.Select(x => (long)x.Value).ToList();
} }
if (nodeIn.ItemType.Definition == StringType) if (nodeIn.ItemType.Definition == StringType)
{ {
return new FilterValue(nodeIn.Collection.Select(x => (string)x.Value).ToList()); return nodeIn.Collection.Select(x => (string)x.Value).ToList();
} }
throw new NotSupportedException(); throw new NotSupportedException();
} }
public override FilterValue Visit(ConstantNode nodeIn) public override ClrValue Visit(ConstantNode nodeIn)
{ {
if (nodeIn.TypeReference.Definition == BooleanType) if (nodeIn.TypeReference.Definition == BooleanType)
{ {
return new FilterValue((bool)nodeIn.Value); return (bool)nodeIn.Value;
} }
if (nodeIn.TypeReference.Definition == SingleType) if (nodeIn.TypeReference.Definition == SingleType)
{ {
return new FilterValue((float)nodeIn.Value); return (float)nodeIn.Value;
} }
if (nodeIn.TypeReference.Definition == DoubleType) if (nodeIn.TypeReference.Definition == DoubleType)
{ {
return new FilterValue((double)nodeIn.Value); return (double)nodeIn.Value;
} }
if (nodeIn.TypeReference.Definition == Int32Type) if (nodeIn.TypeReference.Definition == Int32Type)
{ {
return new FilterValue((int)nodeIn.Value); return (int)nodeIn.Value;
} }
if (nodeIn.TypeReference.Definition == Int64Type) if (nodeIn.TypeReference.Definition == Int64Type)
{ {
return new FilterValue((long)nodeIn.Value); return (long)nodeIn.Value;
} }
if (nodeIn.TypeReference.Definition == StringType) if (nodeIn.TypeReference.Definition == StringType)
{ {
return new FilterValue((string)nodeIn.Value); return (string)nodeIn.Value;
} }
throw new NotSupportedException(); throw new NotSupportedException();

4
src/Squidex.Infrastructure/Queries/OData/EdmModelExtensions.cs

@ -43,9 +43,9 @@ namespace Squidex.Infrastructure.Queries.OData
return parser; return parser;
} }
public static Query ToQuery(this ODataUriParser parser) public static ClrQuery ToQuery(this ODataUriParser parser)
{ {
var query = new Query(); var query = new ClrQuery();
if (parser != null) if (parser != null)
{ {

2
src/Squidex.Infrastructure/Queries/OData/FilterBuilder.cs

@ -12,7 +12,7 @@ namespace Squidex.Infrastructure.Queries.OData
{ {
public static class FilterBuilder public static class FilterBuilder
{ {
public static void ParseFilter(this ODataUriParser query, Query result) public static void ParseFilter(this ODataUriParser query, ClrQuery result)
{ {
SearchClause search; SearchClause search;
try try

46
src/Squidex.Infrastructure/Queries/OData/FilterVisitor.cs

@ -11,7 +11,7 @@ using Microsoft.OData.UriParser;
namespace Squidex.Infrastructure.Queries.OData namespace Squidex.Infrastructure.Queries.OData
{ {
public sealed class FilterVisitor : QueryNodeVisitor<FilterNode> public sealed class FilterVisitor : QueryNodeVisitor<FilterNode<ClrValue>>
{ {
private static readonly FilterVisitor Instance = new FilterVisitor(); private static readonly FilterVisitor Instance = new FilterVisitor();
@ -19,40 +19,40 @@ namespace Squidex.Infrastructure.Queries.OData
{ {
} }
public static FilterNode Visit(QueryNode node) public static FilterNode<ClrValue> Visit(QueryNode node)
{ {
return node.Accept(Instance); return node.Accept(Instance);
} }
public override FilterNode Visit(ConvertNode nodeIn) public override FilterNode<ClrValue> Visit(ConvertNode nodeIn)
{ {
return nodeIn.Source.Accept(this); return nodeIn.Source.Accept(this);
} }
public override FilterNode Visit(UnaryOperatorNode nodeIn) public override FilterNode<ClrValue> Visit(UnaryOperatorNode nodeIn)
{ {
if (nodeIn.OperatorKind == UnaryOperatorKind.Not) if (nodeIn.OperatorKind == UnaryOperatorKind.Not)
{ {
return new FilterNegate(nodeIn.Operand.Accept(this)); return ClrFilter.Not(nodeIn.Operand.Accept(this));
} }
throw new NotSupportedException(); throw new NotSupportedException();
} }
public override FilterNode Visit(InNode nodeIn) public override FilterNode<ClrValue> Visit(InNode nodeIn)
{ {
var value = ConstantWithTypeVisitor.Visit(nodeIn.Right); var value = ConstantWithTypeVisitor.Visit(nodeIn.Right);
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.In, value); return ClrFilter.In(PropertyPathVisitor.Visit(nodeIn.Left), value);
} }
public override FilterNode Visit(SingleValueFunctionCallNode nodeIn) public override FilterNode<ClrValue> Visit(SingleValueFunctionCallNode nodeIn)
{ {
var fieldNode = nodeIn.Parameters.ElementAt(0); var fieldNode = nodeIn.Parameters.ElementAt(0);
if (string.Equals(nodeIn.Name, "empty", StringComparison.OrdinalIgnoreCase)) if (string.Equals(nodeIn.Name, "empty", StringComparison.OrdinalIgnoreCase))
{ {
return new FilterComparison(PropertyPathVisitor.Visit(fieldNode), FilterOperator.Empty, FilterValue.Null); return ClrFilter.Empty(PropertyPathVisitor.Visit(fieldNode));
} }
var valueNode = nodeIn.Parameters.ElementAt(1); var valueNode = nodeIn.Parameters.ElementAt(1);
@ -61,36 +61,36 @@ namespace Squidex.Infrastructure.Queries.OData
{ {
var value = ConstantWithTypeVisitor.Visit(valueNode); var value = ConstantWithTypeVisitor.Visit(valueNode);
return new FilterComparison(PropertyPathVisitor.Visit(fieldNode), FilterOperator.EndsWith, value); return ClrFilter.EndsWith(PropertyPathVisitor.Visit(fieldNode), value);
} }
if (string.Equals(nodeIn.Name, "startswith", StringComparison.OrdinalIgnoreCase)) if (string.Equals(nodeIn.Name, "startswith", StringComparison.OrdinalIgnoreCase))
{ {
var value = ConstantWithTypeVisitor.Visit(valueNode); var value = ConstantWithTypeVisitor.Visit(valueNode);
return new FilterComparison(PropertyPathVisitor.Visit(fieldNode), FilterOperator.StartsWith, value); return ClrFilter.StartsWith(PropertyPathVisitor.Visit(fieldNode), value);
} }
if (string.Equals(nodeIn.Name, "contains", StringComparison.OrdinalIgnoreCase)) if (string.Equals(nodeIn.Name, "contains", StringComparison.OrdinalIgnoreCase))
{ {
var value = ConstantWithTypeVisitor.Visit(valueNode); var value = ConstantWithTypeVisitor.Visit(valueNode);
return new FilterComparison(PropertyPathVisitor.Visit(fieldNode), FilterOperator.Contains, value); return ClrFilter.Contains(PropertyPathVisitor.Visit(fieldNode), value);
} }
throw new NotSupportedException(); throw new NotSupportedException();
} }
public override FilterNode Visit(BinaryOperatorNode nodeIn) public override FilterNode<ClrValue> Visit(BinaryOperatorNode nodeIn)
{ {
if (nodeIn.OperatorKind == BinaryOperatorKind.And) if (nodeIn.OperatorKind == BinaryOperatorKind.And)
{ {
return new FilterJunction(FilterJunctionType.And, nodeIn.Left.Accept(this), nodeIn.Right.Accept(this)); return ClrFilter.And(nodeIn.Left.Accept(this), nodeIn.Right.Accept(this));
} }
if (nodeIn.OperatorKind == BinaryOperatorKind.Or) if (nodeIn.OperatorKind == BinaryOperatorKind.Or)
{ {
return new FilterJunction(FilterJunctionType.Or, nodeIn.Left.Accept(this), nodeIn.Right.Accept(this)); return ClrFilter.Or(nodeIn.Left.Accept(this), nodeIn.Right.Accept(this));
} }
if (nodeIn.Left is SingleValueFunctionCallNode functionNode) if (nodeIn.Left is SingleValueFunctionCallNode functionNode)
@ -99,12 +99,12 @@ namespace Squidex.Infrastructure.Queries.OData
var value = ConstantWithTypeVisitor.Visit(nodeIn.Right); var value = ConstantWithTypeVisitor.Visit(nodeIn.Right);
if (value.ValueType == FilterValueType.Boolean && value.Value is bool booleanRight) if (value.ValueType == ClrValueType.Boolean && value.Value is bool booleanRight)
{ {
if ((nodeIn.OperatorKind == BinaryOperatorKind.Equal && !booleanRight) || if ((nodeIn.OperatorKind == BinaryOperatorKind.Equal && !booleanRight) ||
(nodeIn.OperatorKind == BinaryOperatorKind.NotEqual && booleanRight)) (nodeIn.OperatorKind == BinaryOperatorKind.NotEqual && booleanRight))
{ {
regexFilter = new FilterNegate(regexFilter); regexFilter = ClrFilter.Not(regexFilter);
} }
return regexFilter; return regexFilter;
@ -116,42 +116,42 @@ namespace Squidex.Infrastructure.Queries.OData
{ {
var value = ConstantWithTypeVisitor.Visit(nodeIn.Right); var value = ConstantWithTypeVisitor.Visit(nodeIn.Right);
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.NotEquals, value); return ClrFilter.Ne(PropertyPathVisitor.Visit(nodeIn.Left), value);
} }
if (nodeIn.OperatorKind == BinaryOperatorKind.Equal) if (nodeIn.OperatorKind == BinaryOperatorKind.Equal)
{ {
var value = ConstantWithTypeVisitor.Visit(nodeIn.Right); var value = ConstantWithTypeVisitor.Visit(nodeIn.Right);
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.Equals, value); return ClrFilter.Eq(PropertyPathVisitor.Visit(nodeIn.Left), value);
} }
if (nodeIn.OperatorKind == BinaryOperatorKind.LessThan) if (nodeIn.OperatorKind == BinaryOperatorKind.LessThan)
{ {
var value = ConstantWithTypeVisitor.Visit(nodeIn.Right); var value = ConstantWithTypeVisitor.Visit(nodeIn.Right);
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.LessThan, value); return ClrFilter.Lt(PropertyPathVisitor.Visit(nodeIn.Left), value);
} }
if (nodeIn.OperatorKind == BinaryOperatorKind.LessThanOrEqual) if (nodeIn.OperatorKind == BinaryOperatorKind.LessThanOrEqual)
{ {
var value = ConstantWithTypeVisitor.Visit(nodeIn.Right); var value = ConstantWithTypeVisitor.Visit(nodeIn.Right);
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.LessThanOrEqual, value); return ClrFilter.Le(PropertyPathVisitor.Visit(nodeIn.Left), value);
} }
if (nodeIn.OperatorKind == BinaryOperatorKind.GreaterThan) if (nodeIn.OperatorKind == BinaryOperatorKind.GreaterThan)
{ {
var value = ConstantWithTypeVisitor.Visit(nodeIn.Right); var value = ConstantWithTypeVisitor.Visit(nodeIn.Right);
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.GreaterThan, value); return ClrFilter.Gt(PropertyPathVisitor.Visit(nodeIn.Left), value);
} }
if (nodeIn.OperatorKind == BinaryOperatorKind.GreaterThanOrEqual) if (nodeIn.OperatorKind == BinaryOperatorKind.GreaterThanOrEqual)
{ {
var value = ConstantWithTypeVisitor.Visit(nodeIn.Right); var value = ConstantWithTypeVisitor.Visit(nodeIn.Right);
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.GreaterThanOrEqual, value); return ClrFilter.Ge(PropertyPathVisitor.Visit(nodeIn.Left), value);
} }
} }

4
src/Squidex.Infrastructure/Queries/OData/LimitExtensions.cs

@ -11,7 +11,7 @@ namespace Squidex.Infrastructure.Queries.OData
{ {
public static class LimitExtensions public static class LimitExtensions
{ {
public static void ParseTake(this ODataUriParser query, Query result) public static void ParseTake(this ODataUriParser query, ClrQuery result)
{ {
var top = query.ParseTop(); var top = query.ParseTop();
@ -21,7 +21,7 @@ namespace Squidex.Infrastructure.Queries.OData
} }
} }
public static void ParseSkip(this ODataUriParser query, Query result) public static void ParseSkip(this ODataUriParser query, ClrQuery result)
{ {
var skip = query.ParseSkip(); var skip = query.ParseSkip();

2
src/Squidex.Infrastructure/Queries/OData/SortBuilder.cs

@ -11,7 +11,7 @@ namespace Squidex.Infrastructure.Queries.OData
{ {
public static class SortBuilder public static class SortBuilder
{ {
public static void ParseSort(this ODataUriParser query, Query result) public static void ParseSort(this ODataUriParser query, ClrQuery result)
{ {
var orderBy = query.ParseOrderBy(); var orderBy = query.ParseOrderBy();

10
src/Squidex.Infrastructure/Queries/PascalCasePathConverter.cs

@ -9,22 +9,22 @@ using System.Linq;
namespace Squidex.Infrastructure.Queries namespace Squidex.Infrastructure.Queries
{ {
public sealed class PascalCasePathConverter : TransformVisitor public sealed class PascalCasePathConverter<TValue> : TransformVisitor<TValue>
{ {
private static readonly PascalCasePathConverter Instance = new PascalCasePathConverter(); private static readonly PascalCasePathConverter<TValue> Instance = new PascalCasePathConverter<TValue>();
private PascalCasePathConverter() private PascalCasePathConverter()
{ {
} }
public static FilterNode Transform(FilterNode node) public static FilterNode<TValue> Transform(FilterNode<TValue> node)
{ {
return node.Accept(Instance); return node.Accept(Instance);
} }
public override FilterNode Visit(FilterComparison nodeIn) public override FilterNode<TValue> Visit(CompareFilter<TValue> nodeIn)
{ {
return new FilterComparison(nodeIn.Lhs.Select(x => x.ToPascalCase()).ToList(), nodeIn.Operator, nodeIn.Rhs); return new CompareFilter<TValue>(nodeIn.Path.Select(x => x.ToPascalCase()).ToList(), nodeIn.Operator, nodeIn.Value);
} }
} }
} }

52
src/Squidex.Infrastructure/Queries/PropertyPath.cs

@ -0,0 +1,52 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Linq;
namespace Squidex.Infrastructure.Queries
{
public sealed class PropertyPath : ReadOnlyCollection<string>
{
public PropertyPath(IList<string> items)
: base(items)
{
if (items.Count == 0)
{
throw new ArgumentException("Path cannot be empty.", nameof(items));
}
}
public static implicit operator PropertyPath(string path)
{
return new PropertyPath(path?.Split('.', '/')?.ToList());
}
public static implicit operator PropertyPath(string[] path)
{
return new PropertyPath(path?.ToList());
}
public static implicit operator PropertyPath(List<string> path)
{
return new PropertyPath(path);
}
public static implicit operator PropertyPath(ImmutableList<string> path)
{
return new PropertyPath(path);
}
public override string ToString()
{
return string.Join(".", this);
}
}
}

4
src/Squidex.Infrastructure/Queries/Query.cs

@ -9,9 +9,9 @@ using System.Collections.Generic;
namespace Squidex.Infrastructure.Queries namespace Squidex.Infrastructure.Queries
{ {
public sealed class Query public class Query<TValue>
{ {
public FilterNode Filter { get; set; } public FilterNode<TValue> Filter { get; set; }
public string FullText { get; set; } public string FullText { get; set; }

7
src/Squidex.Infrastructure/Queries/SortNode.cs

@ -5,20 +5,17 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
namespace Squidex.Infrastructure.Queries namespace Squidex.Infrastructure.Queries
{ {
public sealed class SortNode public sealed class SortNode
{ {
public IReadOnlyList<string> Path { get; } public PropertyPath Path { get; }
public SortOrder SortOrder { get; set; } public SortOrder SortOrder { get; set; }
public SortNode(IReadOnlyList<string> path, SortOrder sortOrder) public SortNode(PropertyPath path, SortOrder sortOrder)
{ {
Guard.NotNull(path, nameof(path)); Guard.NotNull(path, nameof(path));
Guard.NotEmpty(path, nameof(path));
Guard.Enum(sortOrder, nameof(sortOrder)); Guard.Enum(sortOrder, nameof(sortOrder));
Path = path; Path = path;

12
src/Squidex.Infrastructure/Queries/TransformVisitor.cs

@ -9,21 +9,21 @@ using System.Linq;
namespace Squidex.Infrastructure.Queries namespace Squidex.Infrastructure.Queries
{ {
public abstract class TransformVisitor : FilterNodeVisitor<FilterNode> public abstract class TransformVisitor<TValue> : FilterNodeVisitor<FilterNode<TValue>, TValue>
{ {
public override FilterNode Visit(FilterComparison nodeIn) public override FilterNode<TValue> Visit(CompareFilter<TValue> nodeIn)
{ {
return nodeIn; return nodeIn;
} }
public override FilterNode Visit(FilterJunction nodeIn) public override FilterNode<TValue> Visit(LogicalFilter<TValue> nodeIn)
{ {
return new FilterJunction(nodeIn.JunctionType, nodeIn.Operands.Select(x => x.Accept(this)).ToList()); return new LogicalFilter<TValue>(nodeIn.Type, nodeIn.Filters.Select(x => x.Accept(this)).ToList());
} }
public override FilterNode Visit(FilterNegate nodeIn) public override FilterNode<TValue> Visit(NegateFilter<TValue> nodeIn)
{ {
return new FilterNegate(nodeIn.Operand.Accept(this)); return new NegateFilter<TValue>(nodeIn.Filter.Accept(this));
} }
} }
} }

12
tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetQueryServiceTests.cs

@ -115,7 +115,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
var enriched1 = new AssetEntity(); var enriched1 = new AssetEntity();
var enriched2 = new AssetEntity(); var enriched2 = new AssetEntity();
A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<Query>.Ignored)) A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<ClrQuery>.Ignored))
.Returns(ResultList.CreateFrom(8, found1, found2)); .Returns(ResultList.CreateFrom(8, found1, found2));
A.CallTo(() => assetEnricher.EnrichAsync(A<IEnumerable<IAssetEntity>>.That.IsSameSequenceAs(found1, found2))) A.CallTo(() => assetEnricher.EnrichAsync(A<IEnumerable<IAssetEntity>>.That.IsSameSequenceAs(found1, found2)))
@ -135,7 +135,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
await sut.QueryAsync(requestContext, query); await sut.QueryAsync(requestContext, query);
A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<Query>.That.Is("FullText: 'Hello World'; Take: 100; Sort: fileName Ascending"))) A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<ClrQuery>.That.Is("FullText: 'Hello World'; Take: 100; Sort: fileName Ascending")))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -146,7 +146,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
await sut.QueryAsync(requestContext, query); await sut.QueryAsync(requestContext, query);
A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<Query>.That.Is("Filter: fileName == 'ABC'; Take: 200; Sort: lastModified Descending"))) A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<ClrQuery>.That.Is("Filter: fileName == 'ABC'; Take: 200; Sort: lastModified Descending")))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -157,7 +157,8 @@ namespace Squidex.Domain.Apps.Entities.Assets
await sut.QueryAsync(requestContext, query); await sut.QueryAsync(requestContext, query);
A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<Query>.That.Is("Take: 30; Sort: lastModified Descending"))) A.CallTo(() => assetRepository.QueryAsync(appId.Id,
A<ClrQuery>.That.Is("Take: 30; Sort: lastModified Descending")))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -168,7 +169,8 @@ namespace Squidex.Domain.Apps.Entities.Assets
await sut.QueryAsync(requestContext, query); await sut.QueryAsync(requestContext, query);
A.CallTo(() => assetRepository.QueryAsync(appId.Id, A<Query>.That.Is("Skip: 20; Take: 200; Sort: lastModified Descending"))) A.CallTo(() => assetRepository.QueryAsync(appId.Id,
A<ClrQuery>.That.Is("Skip: 20; Take: 200; Sort: lastModified Descending")))
.MustHaveHappened(); .MustHaveHappened();
} }
} }

42
tests/Squidex.Domain.Apps.Entities.Tests/Assets/MongoDb/MongoDbQueryTests.cs

@ -17,7 +17,7 @@ using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.MongoDb.Queries;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
using Xunit; using Xunit;
using FilterBuilder = Squidex.Infrastructure.Queries.FilterBuilder; using ClrFilter = Squidex.Infrastructure.Queries.ClrFilter;
using SortBuilder = Squidex.Infrastructure.Queries.SortBuilder; using SortBuilder = Squidex.Infrastructure.Queries.SortBuilder;
namespace Squidex.Domain.Apps.Entities.Assets.MongoDb namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
@ -35,13 +35,13 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
[Fact] [Fact]
public void Should_throw_exception_for_full_text_search() public void Should_throw_exception_for_full_text_search()
{ {
Assert.Throws<ValidationException>(() => Q(new Query { FullText = "Full Text" })); Assert.Throws<ValidationException>(() => Q(new ClrQuery { FullText = "Full Text" }));
} }
[Fact] [Fact]
public void Should_make_query_with_lastModified() public void Should_make_query_with_lastModified()
{ {
var i = F(FilterBuilder.Eq("lastModified", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); var i = F(ClrFilter.Eq("lastModified", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value));
var o = C("{ 'LastModified' : ISODate('1988-01-19T12:00:00Z') }"); var o = C("{ 'LastModified' : ISODate('1988-01-19T12:00:00Z') }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
[Fact] [Fact]
public void Should_make_query_with_lastModifiedBy() public void Should_make_query_with_lastModifiedBy()
{ {
var i = F(FilterBuilder.Eq("lastModifiedBy", "Me")); var i = F(ClrFilter.Eq("lastModifiedBy", "Me"));
var o = C("{ 'LastModifiedBy' : 'Me' }"); var o = C("{ 'LastModifiedBy' : 'Me' }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
[Fact] [Fact]
public void Should_make_query_with_created() public void Should_make_query_with_created()
{ {
var i = F(FilterBuilder.Eq("created", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); var i = F(ClrFilter.Eq("created", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value));
var o = C("{ 'Created' : ISODate('1988-01-19T12:00:00Z') }"); var o = C("{ 'Created' : ISODate('1988-01-19T12:00:00Z') }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
[Fact] [Fact]
public void Should_make_query_with_createdBy() public void Should_make_query_with_createdBy()
{ {
var i = F(FilterBuilder.Eq("createdBy", "Me")); var i = F(ClrFilter.Eq("createdBy", "Me"));
var o = C("{ 'CreatedBy' : 'Me' }"); var o = C("{ 'CreatedBy' : 'Me' }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -77,7 +77,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
[Fact] [Fact]
public void Should_make_query_with_version() public void Should_make_query_with_version()
{ {
var i = F(FilterBuilder.Eq("version", 0)); var i = F(ClrFilter.Eq("version", 0));
var o = C("{ 'Version' : NumberLong(0) }"); var o = C("{ 'Version' : NumberLong(0) }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -86,7 +86,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
[Fact] [Fact]
public void Should_make_query_with_fileVersion() public void Should_make_query_with_fileVersion()
{ {
var i = F(FilterBuilder.Eq("fileVersion", 2)); var i = F(ClrFilter.Eq("fileVersion", 2));
var o = C("{ 'FileVersion' : NumberLong(2) }"); var o = C("{ 'FileVersion' : NumberLong(2) }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -95,7 +95,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
[Fact] [Fact]
public void Should_make_query_with_tags() public void Should_make_query_with_tags()
{ {
var i = F(FilterBuilder.Eq("tags", "tag1")); var i = F(ClrFilter.Eq("tags", "tag1"));
var o = C("{ 'Tags' : 'tag1' }"); var o = C("{ 'Tags' : 'tag1' }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -104,7 +104,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
[Fact] [Fact]
public void Should_make_query_with_fileName() public void Should_make_query_with_fileName()
{ {
var i = F(FilterBuilder.Eq("fileName", "Logo.png")); var i = F(ClrFilter.Eq("fileName", "Logo.png"));
var o = C("{ 'FileName' : 'Logo.png' }"); var o = C("{ 'FileName' : 'Logo.png' }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -113,7 +113,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
[Fact] [Fact]
public void Should_make_query_with_isImage() public void Should_make_query_with_isImage()
{ {
var i = F(FilterBuilder.Eq("isImage", true)); var i = F(ClrFilter.Eq("isImage", true));
var o = C("{ 'IsImage' : true }"); var o = C("{ 'IsImage' : true }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -122,7 +122,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
[Fact] [Fact]
public void Should_make_query_with_mimeType() public void Should_make_query_with_mimeType()
{ {
var i = F(FilterBuilder.Eq("mimeType", "text/json")); var i = F(ClrFilter.Eq("mimeType", "text/json"));
var o = C("{ 'MimeType' : 'text/json' }"); var o = C("{ 'MimeType' : 'text/json' }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -131,7 +131,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
[Fact] [Fact]
public void Should_make_query_with_fileSize() public void Should_make_query_with_fileSize()
{ {
var i = F(FilterBuilder.Eq("fileSize", 1024)); var i = F(ClrFilter.Eq("fileSize", 1024));
var o = C("{ 'FileSize' : NumberLong(1024) }"); var o = C("{ 'FileSize' : NumberLong(1024) }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -140,7 +140,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
[Fact] [Fact]
public void Should_make_query_with_pixelHeight() public void Should_make_query_with_pixelHeight()
{ {
var i = F(FilterBuilder.Eq("pixelHeight", 600)); var i = F(ClrFilter.Eq("pixelHeight", 600));
var o = C("{ 'PixelHeight' : 600 }"); var o = C("{ 'PixelHeight' : 600 }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -149,7 +149,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
[Fact] [Fact]
public void Should_make_query_with_pixelWidth() public void Should_make_query_with_pixelWidth()
{ {
var i = F(FilterBuilder.Eq("pixelWidth", 800)); var i = F(ClrFilter.Eq("pixelWidth", 800));
var o = C("{ 'PixelWidth' : 800 }"); var o = C("{ 'PixelWidth' : 800 }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -176,7 +176,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
[Fact] [Fact]
public void Should_make_take_statement() public void Should_make_take_statement()
{ {
var query = new Query { Take = 3 }; var query = new ClrQuery { Take = 3 };
var cursor = A.Fake<IFindFluent<MongoAssetEntity, MongoAssetEntity>>(); var cursor = A.Fake<IFindFluent<MongoAssetEntity, MongoAssetEntity>>();
cursor.AssetTake(query.AdjustToModel()); cursor.AssetTake(query.AdjustToModel());
@ -188,7 +188,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
[Fact] [Fact]
public void Should_make_skip_statement() public void Should_make_skip_statement()
{ {
var query = new Query { Skip = 3 }; var query = new ClrQuery { Skip = 3 };
var cursor = A.Fake<IFindFluent<MongoAssetEntity, MongoAssetEntity>>(); var cursor = A.Fake<IFindFluent<MongoAssetEntity, MongoAssetEntity>>();
cursor.AssetSkip(query.AdjustToModel()); cursor.AssetSkip(query.AdjustToModel());
@ -202,9 +202,9 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
return value.Replace('\'', '"'); return value.Replace('\'', '"');
} }
private static string F(FilterNode filter) private static string F(FilterNode<ClrValue> filter)
{ {
return Q(new Query { Filter = filter }); return Q(new ClrQuery { Filter = filter });
} }
private static string S(params SortNode[] sorts) private static string S(params SortNode[] sorts)
@ -219,12 +219,12 @@ namespace Squidex.Domain.Apps.Entities.Assets.MongoDb
i = sortDefinition.Render(Serializer, Registry).ToString(); i = sortDefinition.Render(Serializer, Registry).ToString();
}); });
cursor.AssetSort(new Query { Sort = sorts.ToList() }.AdjustToModel()); cursor.AssetSort(new ClrQuery { Sort = sorts.ToList() }.AdjustToModel());
return i; return i;
} }
private static string Q(Query query) private static string Q(ClrQuery query)
{ {
var rendered = var rendered =
query.AdjustToModel().BuildFilter<MongoAssetEntity>(false).Filter query.AdjustToModel().BuildFilter<MongoAssetEntity>(false).Filter

6
tests/Squidex.Domain.Apps.Entities.Tests/Assets/Queries/FilterTagTransformerTests.cs

@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
A.CallTo(() => tagService.GetTagIdsAsync(appId, TagGroups.Assets, A<HashSet<string>>.That.Contains("name1"))) A.CallTo(() => tagService.GetTagIdsAsync(appId, TagGroups.Assets, A<HashSet<string>>.That.Contains("name1")))
.Returns(new Dictionary<string, string> { ["name1"] = "id1" }); .Returns(new Dictionary<string, string> { ["name1"] = "id1" });
var source = FilterBuilder.Eq("tags", "name1"); var source = ClrFilter.Eq("tags", "name1");
var result = FilterTagTransformer.Transform(source, appId, tagService); var result = FilterTagTransformer.Transform(source, appId, tagService);
Assert.Equal("tags == 'id1'", result.ToString()); Assert.Equal("tags == 'id1'", result.ToString());
@ -37,7 +37,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
A.CallTo(() => tagService.GetTagIdsAsync(appId, TagGroups.Assets, A<HashSet<string>>.That.Contains("name1"))) A.CallTo(() => tagService.GetTagIdsAsync(appId, TagGroups.Assets, A<HashSet<string>>.That.Contains("name1")))
.Returns(new Dictionary<string, string>()); .Returns(new Dictionary<string, string>());
var source = FilterBuilder.Eq("tags", "name1"); var source = ClrFilter.Eq("tags", "name1");
var result = FilterTagTransformer.Transform(source, appId, tagService); var result = FilterTagTransformer.Transform(source, appId, tagService);
Assert.Equal("tags == 'name1'", result.ToString()); Assert.Equal("tags == 'name1'", result.ToString());
@ -46,7 +46,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
[Fact] [Fact]
public void Should_not_normalize_other_field() public void Should_not_normalize_other_field()
{ {
var source = FilterBuilder.Eq("other", "value"); var source = ClrFilter.Eq("other", "value");
var result = FilterTagTransformer.Transform(source, appId, tagService); var result = FilterTagTransformer.Transform(source, appId, tagService);
Assert.Equal("other == 'value'", result.ToString()); Assert.Equal("other == 'value'", result.ToString());

6
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs

@ -149,7 +149,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
await sut.QueryAsync(requestContext, schemaId.Name, query); await sut.QueryAsync(requestContext, schemaId.Name, query);
A.CallTo(() => contentRepository.QueryAsync(app, schema, A<Status[]>.That.Is(Status.Published), false, A.CallTo(() => contentRepository.QueryAsync(app, schema, A<Status[]>.That.Is(Status.Published), false,
A<Query>.That.Is("Take: 30; Sort: lastModified Descending"), false)) A<ClrQuery>.That.Is("Take: 30; Sort: lastModified Descending"), false))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -166,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
await sut.QueryAsync(requestContext, schemaId.Name, query); await sut.QueryAsync(requestContext, schemaId.Name, query);
A.CallTo(() => contentRepository.QueryAsync(app, schema, A<Status[]>.That.Is(status), false, A.CallTo(() => contentRepository.QueryAsync(app, schema, A<Status[]>.That.Is(status), false,
A<Query>.That.Is("Skip: 20; Take: 200; Sort: lastModified Descending"), false)) A<ClrQuery>.That.Is("Skip: 20; Take: 200; Sort: lastModified Descending"), false))
.MustHaveHappened(); .MustHaveHappened();
} }
@ -482,7 +482,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
private void SetupContents(Status[] status, int count, int total, IContentEntity content, bool inDraft, bool includeDraft) private void SetupContents(Status[] status, int count, int total, IContentEntity content, bool inDraft, bool includeDraft)
{ {
A.CallTo(() => contentRepository.QueryAsync(app, schema, A<Status[]>.That.Is(status), inDraft, A<Query>.Ignored, includeDraft)) A.CallTo(() => contentRepository.QueryAsync(app, schema, A<Status[]>.That.Is(status), inDraft, A<ClrQuery>.Ignored, includeDraft))
.Returns(ResultList.Create(total, Enumerable.Repeat(content, count))); .Returns(ResultList.Create(total, Enumerable.Repeat(content, count)));
} }

45
tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using FakeItEasy; using FakeItEasy;
using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization;
@ -23,7 +24,7 @@ using Squidex.Infrastructure.MongoDb;
using Squidex.Infrastructure.MongoDb.Queries; using Squidex.Infrastructure.MongoDb.Queries;
using Squidex.Infrastructure.Queries; using Squidex.Infrastructure.Queries;
using Xunit; using Xunit;
using FilterBuilder = Squidex.Infrastructure.Queries.FilterBuilder; using ClrFilter = Squidex.Infrastructure.Queries.ClrFilter;
using SortBuilder = Squidex.Infrastructure.Queries.SortBuilder; using SortBuilder = Squidex.Infrastructure.Queries.SortBuilder;
namespace Squidex.Domain.Apps.Entities.Contents.MongoDb namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
@ -78,13 +79,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
[Fact] [Fact]
public void Should_throw_exception_for_invalid_field() public void Should_throw_exception_for_invalid_field()
{ {
Assert.Throws<NotSupportedException>(() => F(FilterBuilder.Eq("data/invalid/iv", "Me"))); Assert.Throws<NotSupportedException>(() => F(ClrFilter.Eq("data/invalid/iv", "Me")));
} }
[Fact] [Fact]
public void Should_make_query_with_lastModified() public void Should_make_query_with_lastModified()
{ {
var i = F(FilterBuilder.Eq("lastModified", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); var i = F(ClrFilter.Eq("lastModified", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value));
var o = C("{ 'mt' : '1988-01-19T12:00:00Z' }"); var o = C("{ 'mt' : '1988-01-19T12:00:00Z' }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -93,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
[Fact] [Fact]
public void Should_make_query_with_lastModifiedBy() public void Should_make_query_with_lastModifiedBy()
{ {
var i = F(FilterBuilder.Eq("lastModifiedBy", "Me")); var i = F(ClrFilter.Eq("lastModifiedBy", "Me"));
var o = C("{ 'mb' : 'Me' }"); var o = C("{ 'mb' : 'Me' }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -102,7 +103,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
[Fact] [Fact]
public void Should_make_query_with_created() public void Should_make_query_with_created()
{ {
var i = F(FilterBuilder.Eq("created", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); var i = F(ClrFilter.Eq("created", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value));
var o = C("{ 'ct' : '1988-01-19T12:00:00Z' }"); var o = C("{ 'ct' : '1988-01-19T12:00:00Z' }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -111,7 +112,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
[Fact] [Fact]
public void Should_make_query_with_createdBy() public void Should_make_query_with_createdBy()
{ {
var i = F(FilterBuilder.Eq("createdBy", "Me")); var i = F(ClrFilter.Eq("createdBy", "Me"));
var o = C("{ 'cb' : 'Me' }"); var o = C("{ 'cb' : 'Me' }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -120,7 +121,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
[Fact] [Fact]
public void Should_make_query_with_version() public void Should_make_query_with_version()
{ {
var i = F(FilterBuilder.Eq("version", 0L)); var i = F(ClrFilter.Eq("version", 0L));
var o = C("{ 'vs' : NumberLong(0) }"); var o = C("{ 'vs' : NumberLong(0) }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -129,7 +130,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
[Fact] [Fact]
public void Should_make_query_with_version_and_list() public void Should_make_query_with_version_and_list()
{ {
var i = F(FilterBuilder.In("version", 0L, 2L, 5L)); var i = F(ClrFilter.In("version", new List<long> { 0L, 2L, 5L }));
var o = C("{ 'vs' : { '$in' : [NumberLong(0), NumberLong(2), NumberLong(5)] } }"); var o = C("{ 'vs' : { '$in' : [NumberLong(0), NumberLong(2), NumberLong(5)] } }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -138,7 +139,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
[Fact] [Fact]
public void Should_make_query_from_draft() public void Should_make_query_from_draft()
{ {
var i = F(FilterBuilder.Eq("data/dashed_field/iv", "Value"), true); var i = F(ClrFilter.Eq("data/dashed_field/iv", "Value"), true);
var o = C("{ 'dd.8.iv' : 'Value' }"); var o = C("{ 'dd.8.iv' : 'Value' }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -147,7 +148,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
[Fact] [Fact]
public void Should_make_query_with_empty_test() public void Should_make_query_with_empty_test()
{ {
var i = F(FilterBuilder.Empty("data/firstName/iv"), true); var i = F(ClrFilter.Empty("data/firstName/iv"), true);
var o = C("{ '$or' : [{ 'dd.1.iv' : { '$exists' : false } }, { 'dd.1.iv' : null }, { 'dd.1.iv' : '' }, { 'dd.1.iv' : [] }] }"); var o = C("{ '$or' : [{ 'dd.1.iv' : { '$exists' : false } }, { 'dd.1.iv' : null }, { 'dd.1.iv' : '' }, { 'dd.1.iv' : [] }] }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -156,7 +157,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
[Fact] [Fact]
public void Should_make_query_with_datetime_data() public void Should_make_query_with_datetime_data()
{ {
var i = F(FilterBuilder.Eq("data/birthday/iv", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); var i = F(ClrFilter.Eq("data/birthday/iv", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value));
var o = C("{ 'do.5.iv' : '1988-01-19T12:00:00Z' }"); var o = C("{ 'do.5.iv' : '1988-01-19T12:00:00Z' }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -165,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
[Fact] [Fact]
public void Should_make_query_with_underscore_field() public void Should_make_query_with_underscore_field()
{ {
var i = F(FilterBuilder.Eq("data/dashed_field/iv", "Value")); var i = F(ClrFilter.Eq("data/dashed_field/iv", "Value"));
var o = C("{ 'do.8.iv' : 'Value' }"); var o = C("{ 'do.8.iv' : 'Value' }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -174,7 +175,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
[Fact] [Fact]
public void Should_make_query_with_references_equals() public void Should_make_query_with_references_equals()
{ {
var i = F(FilterBuilder.Eq("data/friends/iv", "guid")); var i = F(ClrFilter.Eq("data/friends/iv", "guid"));
var o = C("{ 'do.7.iv' : 'guid' }"); var o = C("{ 'do.7.iv' : 'guid' }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -183,7 +184,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
[Fact] [Fact]
public void Should_make_query_with_array_field() public void Should_make_query_with_array_field()
{ {
var i = F(FilterBuilder.Eq("data/hobbies/iv/name", "PC")); var i = F(ClrFilter.Eq("data/hobbies/iv/name", "PC"));
var o = C("{ 'do.9.iv.91' : 'PC' }"); var o = C("{ 'do.9.iv.91' : 'PC' }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -192,7 +193,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
[Fact] [Fact]
public void Should_make_query_with_assets_equals() public void Should_make_query_with_assets_equals()
{ {
var i = F(FilterBuilder.Eq("data/pictures/iv", "guid")); var i = F(ClrFilter.Eq("data/pictures/iv", "guid"));
var o = C("{ 'do.6.iv' : 'guid' }"); var o = C("{ 'do.6.iv' : 'guid' }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -201,7 +202,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
[Fact] [Fact]
public void Should_make_query_with_full_text() public void Should_make_query_with_full_text()
{ {
var i = Q(new Query { FullText = "Hello my World" }); var i = Q(new ClrQuery { FullText = "Hello my World" });
var o = C("{ '$text' : { '$search' : 'Hello my World' } }"); var o = C("{ '$text' : { '$search' : 'Hello my World' } }");
Assert.Equal(o, i); Assert.Equal(o, i);
@ -228,7 +229,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
[Fact] [Fact]
public void Should_make_take_statement() public void Should_make_take_statement()
{ {
var query = new Query { Take = 3 }; var query = new ClrQuery { Take = 3 };
var cursor = A.Fake<IFindFluent<MongoContentEntity, MongoContentEntity>>(); var cursor = A.Fake<IFindFluent<MongoContentEntity, MongoContentEntity>>();
cursor.ContentTake(query.AdjustToModel(schemaDef, false)); cursor.ContentTake(query.AdjustToModel(schemaDef, false));
@ -240,7 +241,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
[Fact] [Fact]
public void Should_make_skip_statement() public void Should_make_skip_statement()
{ {
var query = new Query { Skip = 3 }; var query = new ClrQuery { Skip = 3 };
var cursor = A.Fake<IFindFluent<MongoContentEntity, MongoContentEntity>>(); var cursor = A.Fake<IFindFluent<MongoContentEntity, MongoContentEntity>>();
cursor.ContentSkip(query.AdjustToModel(schemaDef, false)); cursor.ContentSkip(query.AdjustToModel(schemaDef, false));
@ -254,9 +255,9 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
return value.Replace('\'', '"'); return value.Replace('\'', '"');
} }
private string F(FilterNode filter, bool useDraft = false) private string F(FilterNode<ClrValue> filter, bool useDraft = false)
{ {
return Q(new Query { Filter = filter }, useDraft); return Q(new ClrQuery { Filter = filter }, useDraft);
} }
private string S(params SortNode[] sorts) private string S(params SortNode[] sorts)
@ -271,12 +272,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
i = sortDefinition.Render(Serializer, Registry).ToString(); i = sortDefinition.Render(Serializer, Registry).ToString();
}); });
cursor.ContentSort(new Query { Sort = sorts.ToList() }.AdjustToModel(schemaDef, false)); cursor.ContentSort(new ClrQuery { Sort = sorts.ToList() }.AdjustToModel(schemaDef, false));
return i; return i;
} }
private string Q(Query query, bool useDraft = false) private string Q(ClrQuery query, bool useDraft = false)
{ {
var rendered = var rendered =
query.AdjustToModel(schemaDef, useDraft).BuildFilter<MongoContentEntity>().Filter query.AdjustToModel(schemaDef, useDraft).BuildFilter<MongoContentEntity>().Filter

10
tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/FilterTagTransformerTests.cs

@ -43,7 +43,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
A.CallTo(() => tagService.GetTagIdsAsync(appId.Id, TagGroups.Schemas(schemaId.Id), A<HashSet<string>>.That.Contains("name1"))) A.CallTo(() => tagService.GetTagIdsAsync(appId.Id, TagGroups.Schemas(schemaId.Id), A<HashSet<string>>.That.Contains("name1")))
.Returns(new Dictionary<string, string> { ["name1"] = "id1" }); .Returns(new Dictionary<string, string> { ["name1"] = "id1" });
var source = FilterBuilder.Eq("data.tags2.iv", "name1"); var source = ClrFilter.Eq("data.tags2.iv", "name1");
var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService); var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService);
Assert.Equal("data.tags2.iv == 'id1'", result.ToString()); Assert.Equal("data.tags2.iv == 'id1'", result.ToString());
@ -55,7 +55,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
A.CallTo(() => tagService.GetTagIdsAsync(appId.Id, TagGroups.Assets, A<HashSet<string>>.That.Contains("name1"))) A.CallTo(() => tagService.GetTagIdsAsync(appId.Id, TagGroups.Assets, A<HashSet<string>>.That.Contains("name1")))
.Returns(new Dictionary<string, string>()); .Returns(new Dictionary<string, string>());
var source = FilterBuilder.Eq("data.tags2.iv", "name1"); var source = ClrFilter.Eq("data.tags2.iv", "name1");
var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService); var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService);
Assert.Equal("data.tags2.iv == 'name1'", result.ToString()); Assert.Equal("data.tags2.iv == 'name1'", result.ToString());
@ -64,7 +64,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
[Fact] [Fact]
public void Should_not_normalize_other_tags_field() public void Should_not_normalize_other_tags_field()
{ {
var source = FilterBuilder.Eq("data.tags1.iv", "value"); var source = ClrFilter.Eq("data.tags1.iv", "value");
var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService); var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService);
Assert.Equal("data.tags1.iv == 'value'", result.ToString()); Assert.Equal("data.tags1.iv == 'value'", result.ToString());
@ -76,7 +76,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
[Fact] [Fact]
public void Should_not_normalize_other_typed_field() public void Should_not_normalize_other_typed_field()
{ {
var source = FilterBuilder.Eq("data.string.iv", "value"); var source = ClrFilter.Eq("data.string.iv", "value");
var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService); var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService);
Assert.Equal("data.string.iv == 'value'", result.ToString()); Assert.Equal("data.string.iv == 'value'", result.ToString());
@ -88,7 +88,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
[Fact] [Fact]
public void Should_not_normalize_non_data_field() public void Should_not_normalize_non_data_field()
{ {
var source = FilterBuilder.Eq("no.data", "value"); var source = ClrFilter.Eq("no.data", "value");
var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService); var result = FilterTagTransformer.Transform(source, appId.Id, schema, tagService);
Assert.Equal("no.data == 'value'", result.ToString()); Assert.Equal("no.data == 'value'", result.ToString());

2
tests/Squidex.Domain.Apps.Entities.Tests/TestHelpers/AExtensions.cs

@ -12,7 +12,7 @@ namespace Squidex.Domain.Apps.Entities.TestHelpers
{ {
public static class AExtensions public static class AExtensions
{ {
public static Query Is(this INegatableArgumentConstraintManager<Query> that, string query) public static ClrQuery Is(this INegatableArgumentConstraintManager<ClrQuery> that, string query)
{ {
return that.Matches(x => x.ToString() == query); return that.Matches(x => x.ToString() == query);
} }

8
tests/Squidex.Infrastructure.Tests/Queries/PascalCasePathConverterTests.cs

@ -14,8 +14,8 @@ namespace Squidex.Infrastructure.Queries
[Fact] [Fact]
public void Should_convert_property() public void Should_convert_property()
{ {
var source = FilterBuilder.Eq("property", 1); var source = ClrFilter.Eq("property", 1);
var result = PascalCasePathConverter.Transform(source); var result = PascalCasePathConverter<ClrValue>.Transform(source);
Assert.Equal("Property == 1", result.ToString()); Assert.Equal("Property == 1", result.ToString());
} }
@ -23,8 +23,8 @@ namespace Squidex.Infrastructure.Queries
[Fact] [Fact]
public void Should_convert_properties() public void Should_convert_properties()
{ {
var source = FilterBuilder.Eq("root.child", 1); var source = ClrFilter.Eq("root.child", 1);
var result = PascalCasePathConverter.Transform(source); var result = PascalCasePathConverter<ClrValue>.Transform(source);
Assert.Equal("Root.Child == 1", result.ToString()); Assert.Equal("Root.Child == 1", result.ToString());
} }

Loading…
Cancel
Save