mirror of https://github.com/Squidex/squidex.git
56 changed files with 633 additions and 1984 deletions
@ -1,72 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Collections.Generic; |
|
||||
using Squidex.Infrastructure; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Queries |
|
||||
{ |
|
||||
public sealed class FilterComparison : FilterNode |
|
||||
{ |
|
||||
public IReadOnlyList<string> Path { get; } |
|
||||
|
|
||||
public FilterOperator Operator { get; } |
|
||||
|
|
||||
public FilterValueType ValueType { get; } |
|
||||
|
|
||||
public object Value { get; } |
|
||||
|
|
||||
public FilterComparison(IReadOnlyList<string> path, FilterOperator @operator, object value, FilterValueType valueType) |
|
||||
{ |
|
||||
Guard.NotNull(path, nameof(path)); |
|
||||
Guard.NotEmpty(path, nameof(path)); |
|
||||
Guard.Enum(@operator, nameof(@operator)); |
|
||||
Guard.Enum(valueType, nameof(valueType)); |
|
||||
|
|
||||
Path = path; |
|
||||
|
|
||||
Value = value; |
|
||||
ValueType = valueType; |
|
||||
|
|
||||
Operator = @operator; |
|
||||
} |
|
||||
|
|
||||
public override T Accept<T>(FilterNodeVisitor<T> visitor) |
|
||||
{ |
|
||||
return visitor.Visit(this); |
|
||||
} |
|
||||
|
|
||||
public override string ToString() |
|
||||
{ |
|
||||
var path = string.Join(".", Path); |
|
||||
|
|
||||
switch (Operator) |
|
||||
{ |
|
||||
case FilterOperator.Contains: |
|
||||
return $"contains({path}, {Value})"; |
|
||||
case FilterOperator.EndsWith: |
|
||||
return $"endsWith({path}, {Value})"; |
|
||||
case FilterOperator.StartsWith: |
|
||||
return $"startsWith({path}, {Value})"; |
|
||||
case FilterOperator.Equals: |
|
||||
return $"{path} == {Value}"; |
|
||||
case FilterOperator.NotEquals: |
|
||||
return $"{path} != {Value}"; |
|
||||
case FilterOperator.GreaterThan: |
|
||||
return $"{path} > {Value}"; |
|
||||
case FilterOperator.GreaterThanOrEqual: |
|
||||
return $"{path} >= {Value}"; |
|
||||
case FilterOperator.LessThan: |
|
||||
return $"{path} < {Value}"; |
|
||||
case FilterOperator.LessThanOrEqual: |
|
||||
return $"{path} <= {Value}"; |
|
||||
default: |
|
||||
return string.Empty; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,46 +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; |
|
||||
using Squidex.Infrastructure; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.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)})"; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,15 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Queries |
|
||||
{ |
|
||||
public enum FilterJunctionType |
|
||||
{ |
|
||||
And, |
|
||||
Or |
|
||||
} |
|
||||
} |
|
||||
@ -1,33 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Squidex.Infrastructure; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Queries |
|
||||
{ |
|
||||
public sealed class FilterNegate : FilterNode |
|
||||
{ |
|
||||
public FilterNode Operand { get; } |
|
||||
|
|
||||
public FilterNegate(FilterNode operand) |
|
||||
{ |
|
||||
Guard.NotNull(operand, nameof(operand)); |
|
||||
|
|
||||
Operand = operand; |
|
||||
} |
|
||||
|
|
||||
public override T Accept<T>(FilterNodeVisitor<T> visitor) |
|
||||
{ |
|
||||
return visitor.Visit(this); |
|
||||
} |
|
||||
|
|
||||
public override string ToString() |
|
||||
{ |
|
||||
return $"!{Operand}"; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,14 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Queries |
|
||||
{ |
|
||||
public abstract class FilterNode |
|
||||
{ |
|
||||
public abstract T Accept<T>(FilterNodeVisitor<T> visitor); |
|
||||
} |
|
||||
} |
|
||||
@ -1,31 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
|
|
||||
#pragma warning disable RECS0083 // Shows NotImplementedException throws in the quick task bar
|
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Queries |
|
||||
{ |
|
||||
public abstract class FilterNodeVisitor<T> |
|
||||
{ |
|
||||
public virtual T Visit(FilterComparison nodeIn) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
public virtual T Visit(FilterJunction nodeIn) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
public virtual T Visit(FilterNegate nodeIn) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,22 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Queries |
|
||||
{ |
|
||||
public enum FilterOperator |
|
||||
{ |
|
||||
Contains, |
|
||||
EndsWith, |
|
||||
Equals, |
|
||||
GreaterThan, |
|
||||
GreaterThanOrEqual, |
|
||||
LessThan, |
|
||||
LessThanOrEqual, |
|
||||
NotEquals, |
|
||||
StartsWith |
|
||||
} |
|
||||
} |
|
||||
@ -1,21 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Queries |
|
||||
{ |
|
||||
public enum FilterValueType |
|
||||
{ |
|
||||
Boolean, |
|
||||
Guid, |
|
||||
Double, |
|
||||
Instant, |
|
||||
Int32, |
|
||||
Int64, |
|
||||
Single, |
|
||||
String, |
|
||||
} |
|
||||
} |
|
||||
@ -1,113 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using Microsoft.OData; |
|
||||
using Microsoft.OData.Edm; |
|
||||
using Microsoft.OData.UriParser; |
|
||||
using NodaTime; |
|
||||
using NodaTime.Text; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Queries |
|
||||
{ |
|
||||
public sealed class ConstantVisitor : QueryNodeVisitor<(object Value, FilterValueType ValueType)> |
|
||||
{ |
|
||||
private static readonly IEdmPrimitiveType BooleanType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Boolean); |
|
||||
private static readonly IEdmPrimitiveType DateTimeType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.DateTimeOffset); |
|
||||
private static readonly IEdmPrimitiveType DoubleType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Double); |
|
||||
private static readonly IEdmPrimitiveType GuidType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Guid); |
|
||||
private static readonly IEdmPrimitiveType Int32Type = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Int32); |
|
||||
private static readonly IEdmPrimitiveType Int64Type = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Int64); |
|
||||
private static readonly IEdmPrimitiveType SingleType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Single); |
|
||||
private static readonly IEdmPrimitiveType StringType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.String); |
|
||||
|
|
||||
private static readonly ConstantVisitor Instance = new ConstantVisitor(); |
|
||||
|
|
||||
private ConstantVisitor() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public static (object Value, FilterValueType ValueType) Visit(QueryNode node) |
|
||||
{ |
|
||||
return node.Accept(Instance); |
|
||||
} |
|
||||
|
|
||||
public override (object Value, FilterValueType ValueType) Visit(ConvertNode nodeIn) |
|
||||
{ |
|
||||
if (nodeIn.TypeReference.Definition == BooleanType) |
|
||||
{ |
|
||||
return (bool.Parse(Visit(nodeIn.Source).ToString()), FilterValueType.Boolean); |
|
||||
} |
|
||||
|
|
||||
if (nodeIn.TypeReference.Definition == GuidType) |
|
||||
{ |
|
||||
return (Guid.Parse(Visit(nodeIn.Source).ToString()), FilterValueType.Guid); |
|
||||
} |
|
||||
|
|
||||
if (nodeIn.TypeReference.Definition == DateTimeType) |
|
||||
{ |
|
||||
var value = Visit(nodeIn.Source); |
|
||||
|
|
||||
if (value.Value is DateTimeOffset dateTimeOffset) |
|
||||
{ |
|
||||
return (Instant.FromDateTimeOffset(dateTimeOffset), FilterValueType.Instant); |
|
||||
} |
|
||||
|
|
||||
if (value.Value is DateTime dateTime) |
|
||||
{ |
|
||||
return (Instant.FromDateTimeUtc(DateTime.SpecifyKind(dateTime, DateTimeKind.Utc)), FilterValueType.Instant); |
|
||||
} |
|
||||
|
|
||||
if (value.Value is Date date) |
|
||||
{ |
|
||||
return (Instant.FromUtc(date.Year, date.Month, date.Day, 0, 0), FilterValueType.Instant); |
|
||||
} |
|
||||
|
|
||||
var parseResult = InstantPattern.General.Parse(Visit(nodeIn.Source).ToString()); |
|
||||
|
|
||||
if (!parseResult.Success) |
|
||||
{ |
|
||||
throw new ODataException("Datetime is not in a valid format. Use ISO 8601"); |
|
||||
} |
|
||||
|
|
||||
return (parseResult.Value, FilterValueType.Instant); |
|
||||
} |
|
||||
|
|
||||
return base.Visit(nodeIn); |
|
||||
} |
|
||||
|
|
||||
public override (object Value, FilterValueType ValueType) Visit(ConstantNode nodeIn) |
|
||||
{ |
|
||||
if (nodeIn.TypeReference == BooleanType) |
|
||||
{ |
|
||||
return (nodeIn.Value, FilterValueType.Boolean); |
|
||||
} |
|
||||
|
|
||||
if (nodeIn.TypeReference == DoubleType) |
|
||||
{ |
|
||||
return (nodeIn.Value, FilterValueType.Double); |
|
||||
} |
|
||||
|
|
||||
if (nodeIn.TypeReference == Int32Type) |
|
||||
{ |
|
||||
return (nodeIn.Value, FilterValueType.Int32); |
|
||||
} |
|
||||
|
|
||||
if (nodeIn.TypeReference == Int32Type) |
|
||||
{ |
|
||||
return (nodeIn.Value, FilterValueType.Int64); |
|
||||
} |
|
||||
|
|
||||
if (nodeIn.TypeReference == StringType) |
|
||||
{ |
|
||||
return (nodeIn.Value, FilterValueType.String); |
|
||||
} |
|
||||
|
|
||||
throw new NotSupportedException(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,49 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Microsoft.OData; |
|
||||
using Microsoft.OData.UriParser; |
|
||||
using Squidex.Infrastructure; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Queries |
|
||||
{ |
|
||||
public static class FilterBuilder |
|
||||
{ |
|
||||
public static void ParseFilter(this ODataUriParser query, Query result) |
|
||||
{ |
|
||||
SearchClause search; |
|
||||
try |
|
||||
{ |
|
||||
search = query.ParseSearch(); |
|
||||
} |
|
||||
catch (ODataException ex) |
|
||||
{ |
|
||||
throw new ValidationException("Query $search clause not valid.", new ValidationError(ex.Message)); |
|
||||
} |
|
||||
|
|
||||
if (search != null) |
|
||||
{ |
|
||||
result.FullText = SearchTermVisitor.Visit(search.Expression).ToString(); |
|
||||
} |
|
||||
|
|
||||
FilterClause filter; |
|
||||
try |
|
||||
{ |
|
||||
filter = query.ParseFilter(); |
|
||||
} |
|
||||
catch (ODataException ex) |
|
||||
{ |
|
||||
throw new ValidationException("Query $filter clause not valid.", new ValidationError(ex.Message)); |
|
||||
} |
|
||||
|
|
||||
if (filter != null) |
|
||||
{ |
|
||||
result.Filter = FilterVisitor.Visit(filter.Expression); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,153 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Linq; |
|
||||
using Microsoft.OData.UriParser; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Queries |
|
||||
{ |
|
||||
public sealed class FilterVisitor : QueryNodeVisitor<FilterNode> |
|
||||
{ |
|
||||
private static readonly FilterVisitor Instance = new FilterVisitor(); |
|
||||
|
|
||||
private FilterVisitor() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public static FilterNode Visit(QueryNode node) |
|
||||
{ |
|
||||
return node.Accept(Instance); |
|
||||
} |
|
||||
|
|
||||
public override FilterNode Visit(ConvertNode nodeIn) |
|
||||
{ |
|
||||
return nodeIn.Source.Accept(this); |
|
||||
} |
|
||||
|
|
||||
public override FilterNode Visit(UnaryOperatorNode nodeIn) |
|
||||
{ |
|
||||
if (nodeIn.OperatorKind == UnaryOperatorKind.Not) |
|
||||
{ |
|
||||
return new FilterNegate(nodeIn.Operand.Accept(this)); |
|
||||
} |
|
||||
|
|
||||
throw new NotSupportedException(); |
|
||||
} |
|
||||
|
|
||||
public override FilterNode Visit(SingleValueFunctionCallNode nodeIn) |
|
||||
{ |
|
||||
var fieldNode = nodeIn.Parameters.ElementAt(0); |
|
||||
var valueNode = nodeIn.Parameters.ElementAt(1); |
|
||||
|
|
||||
if (string.Equals(nodeIn.Name, "endswith", StringComparison.OrdinalIgnoreCase)) |
|
||||
{ |
|
||||
var (value, valueType) = ConstantVisitor.Visit(valueNode); |
|
||||
|
|
||||
return new FilterComparison(PropertyPathVisitor.Visit(fieldNode), FilterOperator.EndsWith, value, valueType); |
|
||||
} |
|
||||
|
|
||||
if (string.Equals(nodeIn.Name, "startswith", StringComparison.OrdinalIgnoreCase)) |
|
||||
{ |
|
||||
var (value, valueType) = ConstantVisitor.Visit(valueNode); |
|
||||
|
|
||||
return new FilterComparison(PropertyPathVisitor.Visit(fieldNode), FilterOperator.StartsWith, value, valueType); |
|
||||
} |
|
||||
|
|
||||
if (string.Equals(nodeIn.Name, "contains", StringComparison.OrdinalIgnoreCase)) |
|
||||
{ |
|
||||
var (value, valueType) = ConstantVisitor.Visit(valueNode); |
|
||||
|
|
||||
return new FilterComparison(PropertyPathVisitor.Visit(fieldNode), FilterOperator.Contains, value, valueType); |
|
||||
} |
|
||||
|
|
||||
throw new NotSupportedException(); |
|
||||
} |
|
||||
|
|
||||
public override FilterNode Visit(BinaryOperatorNode nodeIn) |
|
||||
{ |
|
||||
if (nodeIn.OperatorKind == BinaryOperatorKind.And) |
|
||||
{ |
|
||||
return new FilterJunction(FilterJunctionType.And, nodeIn.Left.Accept(this), nodeIn.Right.Accept(this)); |
|
||||
} |
|
||||
|
|
||||
if (nodeIn.OperatorKind == BinaryOperatorKind.Or) |
|
||||
{ |
|
||||
return new FilterJunction(FilterJunctionType.Or, nodeIn.Left.Accept(this), nodeIn.Right.Accept(this)); |
|
||||
} |
|
||||
|
|
||||
if (nodeIn.Left is SingleValueFunctionCallNode functionNode) |
|
||||
{ |
|
||||
var regexFilter = Visit(functionNode); |
|
||||
|
|
||||
var value = BuildValue(nodeIn.Right); |
|
||||
|
|
||||
if (value is bool booleanRight) |
|
||||
{ |
|
||||
if ((nodeIn.OperatorKind == BinaryOperatorKind.Equal && !booleanRight) || |
|
||||
(nodeIn.OperatorKind == BinaryOperatorKind.NotEqual && booleanRight)) |
|
||||
{ |
|
||||
regexFilter = new FilterNegate(regexFilter); |
|
||||
} |
|
||||
|
|
||||
return regexFilter; |
|
||||
} |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
if (nodeIn.OperatorKind == BinaryOperatorKind.NotEqual) |
|
||||
{ |
|
||||
var (value, valueType) = ConstantVisitor.Visit(nodeIn.Left); |
|
||||
|
|
||||
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Right), FilterOperator.NotEquals, value, valueType); |
|
||||
} |
|
||||
|
|
||||
if (nodeIn.OperatorKind == BinaryOperatorKind.Equal) |
|
||||
{ |
|
||||
var (value, valueType) = ConstantVisitor.Visit(nodeIn.Left); |
|
||||
|
|
||||
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Right), FilterOperator.Equals, value, valueType); |
|
||||
} |
|
||||
|
|
||||
if (nodeIn.OperatorKind == BinaryOperatorKind.LessThan) |
|
||||
{ |
|
||||
var (value, valueType) = ConstantVisitor.Visit(nodeIn.Left); |
|
||||
|
|
||||
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Right), FilterOperator.LessThan, value, valueType); |
|
||||
} |
|
||||
|
|
||||
if (nodeIn.OperatorKind == BinaryOperatorKind.LessThanOrEqual) |
|
||||
{ |
|
||||
var (value, valueType) = ConstantVisitor.Visit(nodeIn.Left); |
|
||||
|
|
||||
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Right), FilterOperator.LessThanOrEqual, value, valueType); |
|
||||
} |
|
||||
|
|
||||
if (nodeIn.OperatorKind == BinaryOperatorKind.GreaterThan) |
|
||||
{ |
|
||||
var (value, valueType) = ConstantVisitor.Visit(nodeIn.Left); |
|
||||
|
|
||||
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Right), FilterOperator.GreaterThan, value, valueType); |
|
||||
} |
|
||||
|
|
||||
if (nodeIn.OperatorKind == BinaryOperatorKind.GreaterThanOrEqual) |
|
||||
{ |
|
||||
var (value, valueType) = ConstantVisitor.Visit(nodeIn.Left); |
|
||||
|
|
||||
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Right), FilterOperator.GreaterThanOrEqual, value, valueType); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
throw new NotSupportedException(); |
|
||||
} |
|
||||
|
|
||||
private object BuildValue(QueryNode nodeIn) |
|
||||
{ |
|
||||
return ConstantVisitor.Visit(nodeIn); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,34 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Microsoft.OData.UriParser; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Queries |
|
||||
{ |
|
||||
public static class LimitExtensions |
|
||||
{ |
|
||||
public static void ParseTake(this ODataUriParser query, Query result, int maxValue = int.MaxValue) |
|
||||
{ |
|
||||
var top = query.ParseTop(); |
|
||||
|
|
||||
if (top.HasValue) |
|
||||
{ |
|
||||
result.Take = top; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static void ParseSkip(this ODataUriParser query, Query result) |
|
||||
{ |
|
||||
var skip = query.ParseSkip(); |
|
||||
|
|
||||
if (skip.HasValue) |
|
||||
{ |
|
||||
result.Skip = skip; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,56 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Collections.Immutable; |
|
||||
using Microsoft.OData.UriParser; |
|
||||
using Squidex.Infrastructure; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Queries |
|
||||
{ |
|
||||
public sealed class PropertyPathVisitor : QueryNodeVisitor<ImmutableList<string>> |
|
||||
{ |
|
||||
private static readonly PropertyPathVisitor Instance = new PropertyPathVisitor(); |
|
||||
|
|
||||
private PropertyPathVisitor() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public static ImmutableList<string> Visit(QueryNode node) |
|
||||
{ |
|
||||
return node.Accept(Instance); |
|
||||
} |
|
||||
|
|
||||
public override ImmutableList<string> Visit(ConvertNode nodeIn) |
|
||||
{ |
|
||||
return nodeIn.Source.Accept(this); |
|
||||
} |
|
||||
|
|
||||
public override ImmutableList<string> Visit(SingleComplexNode nodeIn) |
|
||||
{ |
|
||||
if (nodeIn.Source is SingleComplexNode) |
|
||||
{ |
|
||||
return nodeIn.Source.Accept(this).Add(nodeIn.Property.Name.ToPascalCase()); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
return ImmutableList.Create(nodeIn.Property.Name.ToPascalCase()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public override ImmutableList<string> Visit(SingleValuePropertyAccessNode nodeIn) |
|
||||
{ |
|
||||
if (nodeIn.Source is SingleComplexNode) |
|
||||
{ |
|
||||
return nodeIn.Source.Accept(this).Add(nodeIn.Property.Name.ToPascalCase()); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
return ImmutableList.Create(nodeIn.Property.Name.ToPascalCase()); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,41 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using Microsoft.OData.UriParser; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Queries |
|
||||
{ |
|
||||
public class SearchTermVisitor : QueryNodeVisitor<string> |
|
||||
{ |
|
||||
private static readonly SearchTermVisitor Instance = new SearchTermVisitor(); |
|
||||
|
|
||||
private SearchTermVisitor() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public static object Visit(QueryNode node) |
|
||||
{ |
|
||||
return node.Accept(Instance); |
|
||||
} |
|
||||
|
|
||||
public override string Visit(BinaryOperatorNode nodeIn) |
|
||||
{ |
|
||||
if (nodeIn.OperatorKind == BinaryOperatorKind.And) |
|
||||
{ |
|
||||
return nodeIn.Left.Accept(this) + " " + nodeIn.Right.Accept(this); |
|
||||
} |
|
||||
|
|
||||
throw new NotSupportedException(); |
|
||||
} |
|
||||
|
|
||||
public override string Visit(SearchTermNode nodeIn) |
|
||||
{ |
|
||||
return nodeIn.Text; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,43 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using Microsoft.OData.UriParser; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Queries |
|
||||
{ |
|
||||
public static class SortBuilder |
|
||||
{ |
|
||||
public static void ParseSort(this ODataUriParser query, Query result) |
|
||||
{ |
|
||||
var orderBy = query.ParseOrderBy(); |
|
||||
|
|
||||
if (orderBy != null) |
|
||||
{ |
|
||||
while (orderBy != null) |
|
||||
{ |
|
||||
result.Sort.Add(OrderBy(orderBy)); |
|
||||
|
|
||||
orderBy = orderBy.ThenBy; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static SortNode OrderBy(OrderByClause clause) |
|
||||
{ |
|
||||
var path = PropertyPathVisitor.Visit(clause.Expression); |
|
||||
|
|
||||
if (clause.Direction == OrderByDirection.Ascending) |
|
||||
{ |
|
||||
return new SortNode(path, SortOrder.Ascending); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
return new SortNode(path, SortOrder.Descending); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,24 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Collections.Generic; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Queries |
|
||||
{ |
|
||||
public sealed class Query |
|
||||
{ |
|
||||
public FilterNode Filter { get; set; } |
|
||||
|
|
||||
public long? Skip { get; set; } |
|
||||
|
|
||||
public long? Take { get; set; } |
|
||||
|
|
||||
public List<SortNode> Sort { get; } = new List<SortNode>(); |
|
||||
|
|
||||
public string FullText { get; set; } |
|
||||
} |
|
||||
} |
|
||||
@ -1,30 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Collections.Generic; |
|
||||
using Squidex.Infrastructure; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Queries |
|
||||
{ |
|
||||
public sealed class SortNode |
|
||||
{ |
|
||||
public IReadOnlyList<string> Path { get; } |
|
||||
|
|
||||
public SortOrder SortOrder { get; set; } |
|
||||
|
|
||||
public SortNode(IReadOnlyList<string> path, SortOrder sortOrder) |
|
||||
{ |
|
||||
Guard.NotNull(path, nameof(path)); |
|
||||
Guard.NotEmpty(path, nameof(path)); |
|
||||
Guard.Enum(sortOrder, nameof(sortOrder)); |
|
||||
|
|
||||
Path = path; |
|
||||
|
|
||||
SortOrder = sortOrder; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,15 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Core.Queries |
|
||||
{ |
|
||||
public enum SortOrder |
|
||||
{ |
|
||||
Ascending, |
|
||||
Descending |
|
||||
} |
|
||||
} |
|
||||
@ -1,38 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Linq; |
|
||||
using Microsoft.OData.Edm; |
|
||||
using Microsoft.OData.UriParser; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities |
|
||||
{ |
|
||||
public static class EdmModelExtensions |
|
||||
{ |
|
||||
public static ODataUriParser ParseQuery(this IEdmModel model, string query) |
|
||||
{ |
|
||||
if (!model.EntityContainer.EntitySets().Any()) |
|
||||
{ |
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
query = query ?? string.Empty; |
|
||||
|
|
||||
var path = model.EntityContainer.EntitySets().First().Path.Path.Split('.').Last(); |
|
||||
|
|
||||
if (query.StartsWith("?", StringComparison.Ordinal)) |
|
||||
{ |
|
||||
query = query.Substring(1); |
|
||||
} |
|
||||
|
|
||||
var parser = new ODataUriParser(model, new Uri($"{path}?{query}", UriKind.Relative)); |
|
||||
|
|
||||
return parser; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,83 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using Microsoft.OData; |
|
||||
using Microsoft.OData.Edm; |
|
||||
using Microsoft.OData.UriParser; |
|
||||
using NodaTime; |
|
||||
using NodaTime.Text; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.MongoDb.OData |
|
||||
{ |
|
||||
public sealed class ConstantVisitor : QueryNodeVisitor<object> |
|
||||
{ |
|
||||
private static readonly IEdmPrimitiveType BooleanType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Boolean); |
|
||||
private static readonly IEdmPrimitiveType DateTimeType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.DateTimeOffset); |
|
||||
private static readonly IEdmPrimitiveType GuidType = EdmCoreModel.Instance.GetPrimitiveType(EdmPrimitiveTypeKind.Guid); |
|
||||
|
|
||||
private static readonly ConstantVisitor Instance = new ConstantVisitor(); |
|
||||
|
|
||||
private ConstantVisitor() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public static object Visit(QueryNode node) |
|
||||
{ |
|
||||
return node.Accept(Instance); |
|
||||
} |
|
||||
|
|
||||
public override object Visit(ConvertNode nodeIn) |
|
||||
{ |
|
||||
if (nodeIn.TypeReference.Definition == BooleanType) |
|
||||
{ |
|
||||
return bool.Parse(Visit(nodeIn.Source).ToString()); |
|
||||
} |
|
||||
|
|
||||
if (nodeIn.TypeReference.Definition == GuidType) |
|
||||
{ |
|
||||
return Guid.Parse(Visit(nodeIn.Source).ToString()); |
|
||||
} |
|
||||
|
|
||||
if (nodeIn.TypeReference.Definition == DateTimeType) |
|
||||
{ |
|
||||
var value = Visit(nodeIn.Source); |
|
||||
|
|
||||
if (value is DateTimeOffset dateTimeOffset) |
|
||||
{ |
|
||||
return Instant.FromDateTimeOffset(dateTimeOffset); |
|
||||
} |
|
||||
|
|
||||
if (value is DateTime dateTime) |
|
||||
{ |
|
||||
return Instant.FromDateTimeUtc(DateTime.SpecifyKind(dateTime, DateTimeKind.Utc)); |
|
||||
} |
|
||||
|
|
||||
if (value is Date date) |
|
||||
{ |
|
||||
return Instant.FromUtc(date.Year, date.Month, date.Day, 0, 0); |
|
||||
} |
|
||||
|
|
||||
var parseResult = InstantPattern.General.Parse(Visit(nodeIn.Source).ToString()); |
|
||||
|
|
||||
if (!parseResult.Success) |
|
||||
{ |
|
||||
throw new ODataException("Datetime is not in a valid format. Use ISO 8601"); |
|
||||
} |
|
||||
|
|
||||
return parseResult.Value; |
|
||||
} |
|
||||
|
|
||||
return base.Visit(nodeIn); |
|
||||
} |
|
||||
|
|
||||
public override object Visit(ConstantNode nodeIn) |
|
||||
{ |
|
||||
return nodeIn.Value; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,32 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Linq; |
|
||||
using Microsoft.OData.UriParser; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.MongoDb.OData |
|
||||
{ |
|
||||
public delegate string ConvertProperty(string[] parts); |
|
||||
|
|
||||
public static class PropertyBuilder |
|
||||
{ |
|
||||
private static readonly ConvertProperty Default = parts => |
|
||||
{ |
|
||||
return string.Join(".", parts).ToPascalCase(); |
|
||||
}; |
|
||||
|
|
||||
public static string BuildFieldDefinition(this QueryNode node, ConvertProperty convertProperty) |
|
||||
{ |
|
||||
convertProperty = convertProperty ?? Default; |
|
||||
|
|
||||
var propertyParts = node.Accept(PropertyNameVisitor.Instance).ToArray(); |
|
||||
var propertyName = convertProperty(propertyParts); |
|
||||
|
|
||||
return propertyName; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,50 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Collections.Immutable; |
|
||||
using Microsoft.OData.UriParser; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.MongoDb.OData |
|
||||
{ |
|
||||
public sealed class PropertyNameVisitor : QueryNodeVisitor<ImmutableList<string>> |
|
||||
{ |
|
||||
public static readonly PropertyNameVisitor Instance = new PropertyNameVisitor(); |
|
||||
|
|
||||
private PropertyNameVisitor() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public override ImmutableList<string> Visit(ConvertNode nodeIn) |
|
||||
{ |
|
||||
return nodeIn.Source.Accept(this); |
|
||||
} |
|
||||
|
|
||||
public override ImmutableList<string> Visit(SingleComplexNode nodeIn) |
|
||||
{ |
|
||||
if (nodeIn.Source is SingleComplexNode) |
|
||||
{ |
|
||||
return nodeIn.Source.Accept(this).Add(nodeIn.Property.Name); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
return ImmutableList.Create(nodeIn.Property.Name); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public override ImmutableList<string> Visit(SingleValuePropertyAccessNode nodeIn) |
|
||||
{ |
|
||||
if (nodeIn.Source is SingleComplexNode) |
|
||||
{ |
|
||||
return nodeIn.Source.Accept(this).Add(nodeIn.Property.Name); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
return ImmutableList.Create(nodeIn.Property.Name); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,41 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using Microsoft.OData.UriParser; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.MongoDb.OData |
|
||||
{ |
|
||||
public class SearchTermVisitor : QueryNodeVisitor<string> |
|
||||
{ |
|
||||
private static readonly SearchTermVisitor Instance = new SearchTermVisitor(); |
|
||||
|
|
||||
private SearchTermVisitor() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public static object Visit(QueryNode node) |
|
||||
{ |
|
||||
return node.Accept(Instance); |
|
||||
} |
|
||||
|
|
||||
public override string Visit(BinaryOperatorNode nodeIn) |
|
||||
{ |
|
||||
if (nodeIn.OperatorKind == BinaryOperatorKind.And) |
|
||||
{ |
|
||||
return nodeIn.Left.Accept(this) + " " + nodeIn.Right.Accept(this); |
|
||||
} |
|
||||
|
|
||||
throw new NotSupportedException(); |
|
||||
} |
|
||||
|
|
||||
public override string Visit(SearchTermNode nodeIn) |
|
||||
{ |
|
||||
return nodeIn.Text; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,32 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
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, object value) |
||||
|
{ |
||||
|
return Binary(path, FilterOperator.Equals, value); |
||||
|
} |
||||
|
|
||||
|
private static FilterComparison Binary(string path, FilterOperator @operator, object value) |
||||
|
{ |
||||
|
return new FilterComparison(path.Split('.', '/'), @operator, value, FilterValueType.String); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,267 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Immutable; |
||||
|
using System.Linq; |
||||
|
using FakeItEasy; |
||||
|
using Microsoft.Extensions.Caching.Memory; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Microsoft.OData.Edm; |
||||
|
using MongoDB.Bson.Serialization; |
||||
|
using MongoDB.Driver; |
||||
|
using NodaTime.Text; |
||||
|
using Squidex.Domain.Apps.Core; |
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Domain.Apps.Entities.Apps; |
||||
|
using Squidex.Domain.Apps.Entities.Contents.Edm; |
||||
|
using Squidex.Domain.Apps.Entities.MongoDb.Contents; |
||||
|
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors; |
||||
|
using Squidex.Domain.Apps.Entities.Schemas; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.MongoDb; |
||||
|
using Squidex.Infrastructure.MongoDb.OData; |
||||
|
using Squidex.Infrastructure.Queries; |
||||
|
using Xunit; |
||||
|
using FilterBuilder = Squidex.Infrastructure.Queries.FilterBuilder; |
||||
|
using SortBuilder = Squidex.Infrastructure.Queries.SortBuilder; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Entities.Contents.MongoDb |
||||
|
{ |
||||
|
public class MongoDbQueryTests |
||||
|
{ |
||||
|
private static readonly IBsonSerializerRegistry Registry = BsonSerializer.SerializerRegistry; |
||||
|
private static readonly IBsonSerializer<MongoContentEntity> Serializer = BsonSerializer.SerializerRegistry.GetSerializer<MongoContentEntity>(); |
||||
|
private readonly Schema schemaDef; |
||||
|
private readonly IEdmModel edmModel; |
||||
|
private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.EN, Language.DE); |
||||
|
|
||||
|
static MongoDbQueryTests() |
||||
|
{ |
||||
|
InstantSerializer.Register(); |
||||
|
} |
||||
|
|
||||
|
public MongoDbQueryTests() |
||||
|
{ |
||||
|
schemaDef = |
||||
|
new Schema("user") |
||||
|
.AddString(1, "firstName", Partitioning.Language, |
||||
|
new StringFieldProperties { Label = "FirstName", IsRequired = true, AllowedValues = ImmutableList.Create("1", "2") }) |
||||
|
.AddString(2, "lastName", Partitioning.Language, |
||||
|
new StringFieldProperties { Hints = "Last Name", Editor = StringFieldEditor.Input }) |
||||
|
.AddBoolean(3, "isAdmin", Partitioning.Invariant, |
||||
|
new BooleanFieldProperties()) |
||||
|
.AddNumber(4, "age", Partitioning.Invariant, |
||||
|
new NumberFieldProperties { MinValue = 1, MaxValue = 10 }) |
||||
|
.AddDateTime(5, "birthday", Partitioning.Invariant, |
||||
|
new DateTimeFieldProperties()) |
||||
|
.AddAssets(6, "pictures", Partitioning.Invariant, |
||||
|
new AssetsFieldProperties()) |
||||
|
.AddReferences(7, "friends", Partitioning.Invariant, |
||||
|
new ReferencesFieldProperties()) |
||||
|
.AddString(8, "dashed-field", Partitioning.Invariant, |
||||
|
new StringFieldProperties()) |
||||
|
.Update(new SchemaProperties { Hints = "The User" }); |
||||
|
|
||||
|
var builder = new EdmModelBuilder(new MemoryCache(Options.Create(new MemoryCacheOptions()))); |
||||
|
|
||||
|
var schema = A.Dummy<ISchemaEntity>(); |
||||
|
A.CallTo(() => schema.Id).Returns(Guid.NewGuid()); |
||||
|
A.CallTo(() => schema.Version).Returns(3); |
||||
|
A.CallTo(() => schema.SchemaDef).Returns(schemaDef); |
||||
|
|
||||
|
var app = A.Dummy<IAppEntity>(); |
||||
|
A.CallTo(() => app.Id).Returns(Guid.NewGuid()); |
||||
|
A.CallTo(() => app.Version).Returns(3); |
||||
|
A.CallTo(() => app.LanguagesConfig).Returns(languagesConfig); |
||||
|
|
||||
|
edmModel = builder.BuildEdmModel(schema, app); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_throw_exception_for_invalid_field() |
||||
|
{ |
||||
|
Assert.Throws<NotSupportedException>(() => F(FilterBuilder.Eq("data/invalid/iv", "Me"))); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_make_query_with_lastModified() |
||||
|
{ |
||||
|
var i = F(FilterBuilder.Eq("lastModified", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); |
||||
|
var o = C("{ 'mt' : '1988-01-19T12:00:00Z' }"); |
||||
|
|
||||
|
Assert.Equal(o, i); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_make_query_with_lastModifiedBy() |
||||
|
{ |
||||
|
var i = F(FilterBuilder.Eq("lastModifiedBy", "Me")); |
||||
|
var o = C("{ 'mb' : 'Me' }"); |
||||
|
|
||||
|
Assert.Equal(o, i); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_make_query_with_created() |
||||
|
{ |
||||
|
var i = F(FilterBuilder.Eq("created", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); |
||||
|
var o = C("{ 'ct' : '1988-01-19T12:00:00Z' }"); |
||||
|
|
||||
|
Assert.Equal(o, i); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_make_query_with_createdBy() |
||||
|
{ |
||||
|
var i = F(FilterBuilder.Eq("createdBy", "Me")); |
||||
|
var o = C("{ 'cb' : 'Me' }"); |
||||
|
|
||||
|
Assert.Equal(o, i); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_make_query_with_version() |
||||
|
{ |
||||
|
var i = F(FilterBuilder.Eq("version", 0L)); |
||||
|
var o = C("{ 'vs' : NumberLong(0) }"); |
||||
|
|
||||
|
Assert.Equal(o, i); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_make_query_from_draft() |
||||
|
{ |
||||
|
var i = F(FilterBuilder.Eq("data/dashed_field/iv", "Value"), true); |
||||
|
var o = C("{ 'dd.8.iv' : 'Value' }"); |
||||
|
|
||||
|
Assert.Equal(o, i); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_make_query_date_field_created() |
||||
|
{ |
||||
|
var i = F(FilterBuilder.Eq("data/birthday/iv", InstantPattern.General.Parse("1988-01-19T12:00:00Z").Value)); |
||||
|
var o = C("{ 'do.5.iv' : '1988-01-19T12:00:00Z' }"); |
||||
|
|
||||
|
Assert.Equal(o, i); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_make_query_with_underscore_field() |
||||
|
{ |
||||
|
var i = F(FilterBuilder.Eq("data/dashed_field/iv", "Value")); |
||||
|
var o = C("{ 'do.8.iv' : 'Value' }"); |
||||
|
|
||||
|
Assert.Equal(o, i); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_make_query_with_references_equals() |
||||
|
{ |
||||
|
var i = F(FilterBuilder.Eq("data/friends/iv", "guid")); |
||||
|
var o = C("{ 'do.7.iv' : 'guid' }"); |
||||
|
|
||||
|
Assert.Equal(o, i); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_make_query_with_assets_equals() |
||||
|
{ |
||||
|
var i = F(FilterBuilder.Eq("data/pictures/iv", "guid")); |
||||
|
var o = C("{ 'do.6.iv' : 'guid' }"); |
||||
|
|
||||
|
Assert.Equal(o, i); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_make_query_with_full_text() |
||||
|
{ |
||||
|
var i = Q(new Query { FullText = "Hello my World" }); |
||||
|
var o = C("{ '$text' : { '$search' : 'Hello my World' } }"); |
||||
|
|
||||
|
Assert.Equal(o, i); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_make_orderby_with_single_field() |
||||
|
{ |
||||
|
var i = S(SortBuilder.Descending("data/age/iv")); |
||||
|
var o = C("{ 'do.4.iv' : -1 }"); |
||||
|
|
||||
|
Assert.Equal(o, i); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_make_orderby_with_multiple_fields() |
||||
|
{ |
||||
|
var i = S(SortBuilder.Ascending("data/age/iv"), SortBuilder.Descending("data/firstName/en")); |
||||
|
var o = C("{ 'do.4.iv' : 1, 'do.1.en' : -1 }"); |
||||
|
|
||||
|
Assert.Equal(o, i); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_make_take_statement() |
||||
|
{ |
||||
|
var query = new Query { Take = 3 }; |
||||
|
var cursor = A.Fake<IFindFluent<MongoContentEntity, MongoContentEntity>>(); |
||||
|
|
||||
|
cursor.ContentTake(query.AdjustToModel(schemaDef, false)); |
||||
|
|
||||
|
A.CallTo(() => cursor.Limit(3)).MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_make_skip_statement() |
||||
|
{ |
||||
|
var query = new Query { Skip = 3 }; |
||||
|
var cursor = A.Fake<IFindFluent<MongoContentEntity, MongoContentEntity>>(); |
||||
|
|
||||
|
cursor.ContentSkip(query.AdjustToModel(schemaDef, false)); |
||||
|
|
||||
|
A.CallTo(() => cursor.Skip(3)).MustHaveHappened(); |
||||
|
} |
||||
|
|
||||
|
private static string C(string value) |
||||
|
{ |
||||
|
return value.Replace('\'', '"'); |
||||
|
} |
||||
|
|
||||
|
private string F(FilterNode filter, bool useDraft = false) |
||||
|
{ |
||||
|
return Q(new Query { Filter = filter }, useDraft); |
||||
|
} |
||||
|
|
||||
|
private string S(params SortNode[] sorts) |
||||
|
{ |
||||
|
var cursor = A.Fake<IFindFluent<MongoContentEntity, MongoContentEntity>>(); |
||||
|
|
||||
|
var i = string.Empty; |
||||
|
|
||||
|
A.CallTo(() => cursor.Sort(A<SortDefinition<MongoContentEntity>>.Ignored)) |
||||
|
.Invokes((SortDefinition<MongoContentEntity> sortDefinition) => |
||||
|
{ |
||||
|
i = sortDefinition.Render(Serializer, Registry).ToString(); |
||||
|
}); |
||||
|
|
||||
|
cursor.ContentSort(new Query { Sort = sorts.ToList() }.AdjustToModel(schemaDef, false)); |
||||
|
|
||||
|
return i; |
||||
|
} |
||||
|
|
||||
|
private string Q(Query query, bool useDraft = false) |
||||
|
{ |
||||
|
var rendered = |
||||
|
query.AdjustToModel(schemaDef, useDraft).BuildFilter<MongoContentEntity>().Filter |
||||
|
.Render(Serializer, Registry).ToString(); |
||||
|
|
||||
|
return rendered; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,448 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex UG (haftungsbeschränkt)
|
|
||||
// All rights reserved. Licensed under the MIT license.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Immutable; |
|
||||
using FakeItEasy; |
|
||||
using Microsoft.Extensions.Caching.Memory; |
|
||||
using Microsoft.Extensions.Options; |
|
||||
using Microsoft.OData.Edm; |
|
||||
using MongoDB.Bson.Serialization; |
|
||||
using MongoDB.Driver; |
|
||||
using Squidex.Domain.Apps.Core; |
|
||||
using Squidex.Domain.Apps.Core.Apps; |
|
||||
using Squidex.Domain.Apps.Core.Schemas; |
|
||||
using Squidex.Domain.Apps.Entities.Apps; |
|
||||
using Squidex.Domain.Apps.Entities.Contents.Edm; |
|
||||
using Squidex.Domain.Apps.Entities.MongoDb.Contents; |
|
||||
using Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors; |
|
||||
using Squidex.Domain.Apps.Entities.Schemas; |
|
||||
using Squidex.Infrastructure; |
|
||||
using Squidex.Infrastructure.MongoDb; |
|
||||
using Squidex.Infrastructure.MongoDb.OData; |
|
||||
using Xunit; |
|
||||
|
|
||||
namespace Squidex.Domain.Apps.Entities.Contents.OData |
|
||||
{ |
|
||||
public class ODataQueryTests |
|
||||
{ |
|
||||
private static readonly IBsonSerializerRegistry Registry = BsonSerializer.SerializerRegistry; |
|
||||
private static readonly IBsonSerializer<MongoContentEntity> Serializer = BsonSerializer.SerializerRegistry.GetSerializer<MongoContentEntity>(); |
|
||||
private readonly Schema schemaDef; |
|
||||
private readonly IEdmModel edmModel; |
|
||||
private readonly LanguagesConfig languagesConfig = LanguagesConfig.Build(Language.EN, Language.DE); |
|
||||
|
|
||||
static ODataQueryTests() |
|
||||
{ |
|
||||
InstantSerializer.Register(); |
|
||||
} |
|
||||
|
|
||||
public ODataQueryTests() |
|
||||
{ |
|
||||
schemaDef = |
|
||||
new Schema("user") |
|
||||
.AddString(1, "firstName", Partitioning.Language, |
|
||||
new StringFieldProperties { Label = "FirstName", IsRequired = true, AllowedValues = ImmutableList.Create("1", "2") }) |
|
||||
.AddString(2, "lastName", Partitioning.Language, |
|
||||
new StringFieldProperties { Hints = "Last Name", Editor = StringFieldEditor.Input }) |
|
||||
.AddBoolean(3, "isAdmin", Partitioning.Invariant, |
|
||||
new BooleanFieldProperties()) |
|
||||
.AddNumber(4, "age", Partitioning.Invariant, |
|
||||
new NumberFieldProperties { MinValue = 1, MaxValue = 10 }) |
|
||||
.AddDateTime(5, "birthday", Partitioning.Invariant, |
|
||||
new DateTimeFieldProperties()) |
|
||||
.AddAssets(6, "pictures", Partitioning.Invariant, |
|
||||
new AssetsFieldProperties()) |
|
||||
.AddReferences(7, "friends", Partitioning.Invariant, |
|
||||
new ReferencesFieldProperties()) |
|
||||
.AddString(8, "dashed-field", Partitioning.Invariant, |
|
||||
new StringFieldProperties()) |
|
||||
.Update(new SchemaProperties { Hints = "The User" }); |
|
||||
|
|
||||
var builder = new EdmModelBuilder(new MemoryCache(Options.Create(new MemoryCacheOptions()))); |
|
||||
|
|
||||
var schema = A.Dummy<ISchemaEntity>(); |
|
||||
A.CallTo(() => schema.Id).Returns(Guid.NewGuid()); |
|
||||
A.CallTo(() => schema.Version).Returns(3); |
|
||||
A.CallTo(() => schema.SchemaDef).Returns(schemaDef); |
|
||||
|
|
||||
var app = A.Dummy<IAppEntity>(); |
|
||||
A.CallTo(() => app.Id).Returns(Guid.NewGuid()); |
|
||||
A.CallTo(() => app.Version).Returns(3); |
|
||||
A.CallTo(() => app.LanguagesConfig).Returns(languagesConfig); |
|
||||
|
|
||||
edmModel = builder.BuildEdmModel(schema, app); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_parse_query() |
|
||||
{ |
|
||||
var parser = edmModel.ParseQuery("$filter=data/firstName/de eq 'Sebastian'"); |
|
||||
|
|
||||
Assert.NotNull(parser); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_created() |
|
||||
{ |
|
||||
var i = F("$filter=created eq 1988-01-19T12:00:00Z"); |
|
||||
var o = C("{ 'ct' : ISODate('1988-01-19T12:00:00Z') }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_created_and_date() |
|
||||
{ |
|
||||
var i = F("$filter=created eq 1988-01-19"); |
|
||||
var o = C("{ 'ct' : ISODate('1988-01-19T00:00:00Z') }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_createdBy() |
|
||||
{ |
|
||||
var i = F("$filter=createdBy eq 'Me'"); |
|
||||
var o = C("{ 'cb' : 'Me' }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_lastModified() |
|
||||
{ |
|
||||
var i = F("$filter=lastModified eq 1988-01-19T12:00:00Z"); |
|
||||
var o = C("{ 'mt' : ISODate('1988-01-19T12:00:00Z') }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_lastModifiedBy() |
|
||||
{ |
|
||||
var i = F("$filter=lastModifiedBy eq 'Me'"); |
|
||||
var o = C("{ 'mb' : 'Me' }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_version() |
|
||||
{ |
|
||||
var i = F("$filter=version eq 0"); |
|
||||
var o = C("{ 'vs' : 0 }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_underscore_field() |
|
||||
{ |
|
||||
var i = F("$filter=data/dashed_field/iv eq 'Value'"); |
|
||||
var o = C("{ 'do.8.iv' : 'Value' }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_not() |
|
||||
{ |
|
||||
var i = F("$filter=not endswith(data/firstName/de, 'Sebastian')"); |
|
||||
var o = C("{ 'do.1.de' : { '$not' : /Sebastian$/i } }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_startswith() |
|
||||
{ |
|
||||
var i = F("$filter=startswith(data/firstName/de, 'Sebastian')"); |
|
||||
var o = C("{ 'do.1.de' : /^Sebastian/i }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_endswith() |
|
||||
{ |
|
||||
var i = F("$filter=endswith(data/firstName/de, 'Sebastian')"); |
|
||||
var o = C("{ 'do.1.de' : /Sebastian$/i }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_cointains() |
|
||||
{ |
|
||||
var i = F("$filter=contains(data/firstName/de, 'Sebastian')"); |
|
||||
var o = C("{ 'do.1.de' : /Sebastian/i }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_equals() |
|
||||
{ |
|
||||
var i = F("$filter=contains(data/firstName/de, 'Sebastian') eq true"); |
|
||||
var o = C("{ 'do.1.de' : /Sebastian/i }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_wih_equals_to_false() |
|
||||
{ |
|
||||
var i = F("$filter=contains(data/firstName/de, 'Sebastian') eq false"); |
|
||||
var o = C("{ 'do.1.de' : { '$not' : /Sebastian/i } }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_conjunction_and_contains() |
|
||||
{ |
|
||||
var i = F("$filter=contains(data/firstName/de, 'Sebastian') eq false and data/isAdmin/iv eq true"); |
|
||||
var o = C("{ 'do.1.de' : { '$not' : /Sebastian/i }, 'do.3.iv' : true }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_string_equals() |
|
||||
{ |
|
||||
var i = F("$filter=data/firstName/de eq 'Sebastian'"); |
|
||||
var o = C("{ 'do.1.de' : 'Sebastian' }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_datetime_equals() |
|
||||
{ |
|
||||
var i = F("$filter=data/birthday/iv eq 1988-01-19T12:00:00Z"); |
|
||||
var o = C("{ 'do.5.iv' : '1988-01-19T12:00:00Z' }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_boolean_equals() |
|
||||
{ |
|
||||
var i = F("$filter=data/isAdmin/iv eq true"); |
|
||||
var o = C("{ 'do.3.iv' : true }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_notequals() |
|
||||
{ |
|
||||
var i = F("$filter=data/firstName/de ne 'Sebastian'"); |
|
||||
var o = C("{ '$or' : [{ 'do.1.de' : { '$exists' : false } }, { 'do.1.de' : { '$ne' : 'Sebastian' } }] }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_lessthan() |
|
||||
{ |
|
||||
var i = F("$filter=data/age/iv lt 1"); |
|
||||
var o = C("{ 'do.4.iv' : { '$lt' : 1.0 } }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_lessequals() |
|
||||
{ |
|
||||
var i = F("$filter=data/age/iv le 1"); |
|
||||
var o = C("{ 'do.4.iv' : { '$lte' : 1.0 } }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_greaterthan() |
|
||||
{ |
|
||||
var i = F("$filter=data/age/iv gt 1"); |
|
||||
var o = C("{ 'do.4.iv' : { '$gt' : 1.0 } }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_greaterequals() |
|
||||
{ |
|
||||
var i = F("$filter=data/age/iv ge 1"); |
|
||||
var o = C("{ 'do.4.iv' : { '$gte' : 1.0 } }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_references_equals() |
|
||||
{ |
|
||||
var i = F("$filter=data/pictures/iv eq 'guid'"); |
|
||||
var o = C("{ 'do.6.iv' : 'guid' }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_assets_equals() |
|
||||
{ |
|
||||
var i = F("$filter=data/friends/iv eq 'guid'"); |
|
||||
var o = C("{ 'do.7.iv' : 'guid' }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_conjunction() |
|
||||
{ |
|
||||
var i = F("$filter=data/age/iv eq 1 and data/age/iv eq 2"); |
|
||||
var o = C("{ '$and' : [{ 'do.4.iv' : 1.0 }, { 'do.4.iv' : 2.0 }] }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_disjunction() |
|
||||
{ |
|
||||
var i = F("$filter=data/age/iv eq 1 or data/age/iv eq 2"); |
|
||||
var o = C("{ '$or' : [{ 'do.4.iv' : 1.0 }, { 'do.4.iv' : 2.0 }] }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_full_text() |
|
||||
{ |
|
||||
var i = F("$search=Hello my World"); |
|
||||
var o = C("{ '$text' : { '$search' : 'Hello my World' } }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_query_with_full_text_and_multiple_terms() |
|
||||
{ |
|
||||
var i = F("$search=A and B"); |
|
||||
var o = C("{ '$text' : { '$search' : 'A and B' } }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_orderby_with_single_field() |
|
||||
{ |
|
||||
var i = S("$orderby=data/age/iv desc"); |
|
||||
var o = C("{ 'do.4.iv' : -1 }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_orderby_with_multiple_field() |
|
||||
{ |
|
||||
var i = S("$orderby=data/age/iv, data/firstName/en desc"); |
|
||||
var o = C("{ 'do.4.iv' : 1, 'do.1.en' : -1 }"); |
|
||||
|
|
||||
Assert.Equal(o, i); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_top_statement() |
|
||||
{ |
|
||||
var parser = edmModel.ParseQuery("$top=3"); |
|
||||
var cursor = A.Fake<IFindFluent<MongoContentEntity, MongoContentEntity>>(); |
|
||||
|
|
||||
cursor.ContentTake(parser); |
|
||||
|
|
||||
A.CallTo(() => cursor.Limit(3)).MustHaveHappened(); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_top_statement_with_limit() |
|
||||
{ |
|
||||
var parser = edmModel.ParseQuery("$top=300"); |
|
||||
var cursor = A.Fake<IFindFluent<MongoContentEntity, MongoContentEntity>>(); |
|
||||
|
|
||||
cursor.ContentTake(parser); |
|
||||
|
|
||||
A.CallTo(() => cursor.Limit(200)).MustHaveHappened(); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_top_statement_with_default_value() |
|
||||
{ |
|
||||
var parser = edmModel.ParseQuery(string.Empty); |
|
||||
var cursor = A.Fake<IFindFluent<MongoContentEntity, MongoContentEntity>>(); |
|
||||
|
|
||||
cursor.ContentTake(parser); |
|
||||
|
|
||||
A.CallTo(() => cursor.Limit(20)).MustHaveHappened(); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_skip_statement() |
|
||||
{ |
|
||||
var parser = edmModel.ParseQuery("$skip=3"); |
|
||||
var cursor = A.Fake<IFindFluent<MongoContentEntity, MongoContentEntity>>(); |
|
||||
|
|
||||
cursor.ContentSkip(parser); |
|
||||
|
|
||||
A.CallTo(() => cursor.Skip(3)).MustHaveHappened(); |
|
||||
} |
|
||||
|
|
||||
[Fact] |
|
||||
public void Should_make_skip_statement_with_default_value() |
|
||||
{ |
|
||||
var parser = edmModel.ParseQuery(string.Empty); |
|
||||
var cursor = A.Fake<IFindFluent<MongoContentEntity, MongoContentEntity>>(); |
|
||||
|
|
||||
cursor.ContentSkip(parser); |
|
||||
|
|
||||
A.CallTo(() => cursor.Skip(A<int>.Ignored)).MustNotHaveHappened(); |
|
||||
} |
|
||||
|
|
||||
private static string C(string value) |
|
||||
{ |
|
||||
return value.Replace('\'', '"'); |
|
||||
} |
|
||||
|
|
||||
private string S(string value) |
|
||||
{ |
|
||||
var parser = edmModel.ParseQuery(value); |
|
||||
var cursor = A.Fake<IFindFluent<MongoContentEntity, MongoContentEntity>>(); |
|
||||
|
|
||||
var i = string.Empty; |
|
||||
|
|
||||
A.CallTo(() => cursor.Sort(A<SortDefinition<MongoContentEntity>>.Ignored)) |
|
||||
.Invokes((SortDefinition<MongoContentEntity> sortDefinition) => |
|
||||
{ |
|
||||
i = sortDefinition.Render(Serializer, Registry).ToString(); |
|
||||
}); |
|
||||
|
|
||||
cursor.ContentSort(parser, FindExtensions.CreatePropertyCalculator(schemaDef, false)); |
|
||||
|
|
||||
return i; |
|
||||
} |
|
||||
|
|
||||
private string F(string value) |
|
||||
{ |
|
||||
var parser = edmModel.ParseQuery(value); |
|
||||
|
|
||||
var query = |
|
||||
parser.BuildFilter<MongoContentEntity>(FindExtensions.CreatePropertyCalculator(schemaDef, false), FindExtensions.ValueConverter) |
|
||||
.Filter.Render(Serializer, Registry).ToString(); |
|
||||
|
|
||||
return query; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue