Browse Source

Support for IN Queries.

pull/320/head
Sebastian Stehle 7 years ago
parent
commit
d24a083154
  1. 10
      src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs
  2. 4
      src/Squidex.Domain.Apps.Entities/Assets/Queries/FilterTagTransformer.cs
  3. 4
      src/Squidex.Domain.Apps.Entities/Contents/Queries/FilterTagTransformer.cs
  4. 19
      src/Squidex.Infrastructure.MongoDb/MongoDb/OData/FilterVisitor.cs
  5. 54
      src/Squidex.Infrastructure/Queries/FilterBuilder.cs
  6. 41
      src/Squidex.Infrastructure/Queries/FilterComparison.cs
  7. 1
      src/Squidex.Infrastructure/Queries/FilterOperator.cs
  8. 138
      src/Squidex.Infrastructure/Queries/FilterValue.cs
  9. 3
      src/Squidex.Infrastructure/Queries/FilterValueType.cs
  10. 117
      src/Squidex.Infrastructure/Queries/OData/ConstantWithTypeVisitor.cs
  11. 47
      src/Squidex.Infrastructure/Queries/OData/FilterVisitor.cs
  12. 2
      src/Squidex.Infrastructure/Queries/PascalCasePathConverter.cs
  13. 9
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs
  14. 72
      tests/Squidex.Infrastructure.Tests/Queries/ODataConversionTests.cs

10
src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Visitors/FindExtensions.cs

@ -39,16 +39,16 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Visitors
public override FilterNode Visit(FilterComparison nodeIn) public override FilterNode Visit(FilterComparison nodeIn)
{ {
var value = nodeIn.Value; var value = nodeIn.Rhs.Value;
if (value is Instant instant && if (value is Instant instant &&
!string.Equals(nodeIn.Path[0], "mt", StringComparison.OrdinalIgnoreCase) && !string.Equals(nodeIn.Lhs[0], "mt", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(nodeIn.Path[0], "ct", StringComparison.OrdinalIgnoreCase)) !string.Equals(nodeIn.Lhs[0], "ct", StringComparison.OrdinalIgnoreCase))
{ {
value = instant.ToString(); return new FilterComparison(pathConverter(nodeIn.Lhs), nodeIn.Operator, new FilterValue(value.ToString()));
} }
return new FilterComparison(pathConverter(nodeIn.Path), nodeIn.Operator, value, nodeIn.ValueType); return new FilterComparison(pathConverter(nodeIn.Lhs), nodeIn.Operator, nodeIn.Rhs);
} }
} }

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

@ -34,13 +34,13 @@ namespace Squidex.Domain.Apps.Entities.Assets.Queries
public override FilterNode Visit(FilterComparison nodeIn) public override FilterNode Visit(FilterComparison nodeIn)
{ {
if (string.Equals(nodeIn.Path[0], nameof(IAssetEntity.Tags), StringComparison.OrdinalIgnoreCase) && nodeIn.Value is string stringValue) if (string.Equals(nodeIn.Lhs[0], nameof(IAssetEntity.Tags), StringComparison.OrdinalIgnoreCase) && nodeIn.Rhs.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.Path, nodeIn.Operator, normalized, FilterValueType.String); return new FilterComparison(nodeIn.Lhs, nodeIn.Operator, new FilterValue(normalized));
} }
} }

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

@ -39,13 +39,13 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries
public override FilterNode Visit(FilterComparison nodeIn) public override FilterNode Visit(FilterComparison nodeIn)
{ {
if (nodeIn.Value is string stringValue && IsDataPath(nodeIn.Path) && IsTagField(nodeIn.Path)) if (nodeIn.Rhs.Value is string stringValue && IsDataPath(nodeIn.Lhs) && IsTagField(nodeIn.Lhs))
{ {
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.Path, nodeIn.Operator, normalized, FilterValueType.String); return new FilterComparison(nodeIn.Lhs, nodeIn.Operator, new FilterValue(normalized));
} }
} }

19
src/Squidex.Infrastructure.MongoDb/MongoDb/OData/FilterVisitor.cs

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Collections;
using System.Linq; using System.Linq;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
@ -46,7 +47,7 @@ namespace Squidex.Infrastructure.MongoDb.OData
public override FilterDefinition<T> Visit(FilterComparison nodeIn) public override FilterDefinition<T> Visit(FilterComparison nodeIn)
{ {
var propertyName = string.Join(".", nodeIn.Path); var propertyName = string.Join(".", nodeIn.Lhs);
switch (nodeIn.Operator) switch (nodeIn.Operator)
{ {
@ -57,17 +58,19 @@ namespace Squidex.Infrastructure.MongoDb.OData
case FilterOperator.EndsWith: case FilterOperator.EndsWith:
return Filter.Regex(propertyName, BuildRegex(nodeIn, s => s + "$")); return Filter.Regex(propertyName, BuildRegex(nodeIn, s => s + "$"));
case FilterOperator.Equals: case FilterOperator.Equals:
return Filter.Eq(propertyName, nodeIn.Value); return Filter.Eq(propertyName, nodeIn.Rhs.Value);
case FilterOperator.GreaterThan: case FilterOperator.GreaterThan:
return Filter.Gt(propertyName, nodeIn.Value); return Filter.Gt(propertyName, nodeIn.Rhs.Value);
case FilterOperator.GreaterThanOrEqual: case FilterOperator.GreaterThanOrEqual:
return Filter.Gte(propertyName, nodeIn.Value); return Filter.Gte(propertyName, nodeIn.Rhs.Value);
case FilterOperator.LessThan: case FilterOperator.LessThan:
return Filter.Lt(propertyName, nodeIn.Value); return Filter.Lt(propertyName, nodeIn.Rhs.Value);
case FilterOperator.LessThanOrEqual: case FilterOperator.LessThanOrEqual:
return Filter.Lte(propertyName, nodeIn.Value); return Filter.Lte(propertyName, nodeIn.Rhs.Value);
case FilterOperator.NotEquals: case FilterOperator.NotEquals:
return Filter.Ne(propertyName, nodeIn.Value); return Filter.Ne(propertyName, nodeIn.Rhs.Value);
case FilterOperator.In:
return Filter.In(propertyName, ((IList)nodeIn.Rhs.Value).OfType<object>());
} }
throw new NotSupportedException(); throw new NotSupportedException();
@ -75,7 +78,7 @@ namespace Squidex.Infrastructure.MongoDb.OData
private static BsonRegularExpression BuildRegex(FilterComparison node, Func<string, string> formatter) private static BsonRegularExpression BuildRegex(FilterComparison node, Func<string, string> formatter)
{ {
return new BsonRegularExpression(formatter(node.Value.ToString()), "i"); return new BsonRegularExpression(formatter(node.Rhs.Value.ToString()), "i");
} }
} }
} }

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

@ -5,6 +5,9 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Linq;
using NodaTime;
namespace Squidex.Infrastructure.Queries namespace Squidex.Infrastructure.Queries
{ {
public static class FilterBuilder public static class FilterBuilder
@ -19,14 +22,59 @@ namespace Squidex.Infrastructure.Queries
return new FilterJunction(FilterJunctionType.Or, operands); return new FilterJunction(FilterJunctionType.Or, operands);
} }
public static FilterComparison Eq(string path, object value) 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); return Binary(path, FilterOperator.Equals, value);
} }
private static FilterComparison Binary(string path, FilterOperator @operator, object value) 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, value, FilterValueType.String); return new FilterComparison(path.Split('.', '/'), @operator, new FilterValue(value));
} }
} }
} }

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

@ -11,25 +11,20 @@ namespace Squidex.Infrastructure.Queries
{ {
public sealed class FilterComparison : FilterNode public sealed class FilterComparison : FilterNode
{ {
public IReadOnlyList<string> Path { get; } public IReadOnlyList<string> Lhs { get; }
public FilterOperator Operator { get; } public FilterOperator Operator { get; }
public FilterValueType ValueType { get; } public FilterValue Rhs { get; }
public object Value { get; } public FilterComparison(IReadOnlyList<string> lhs, FilterOperator @operator, FilterValue rhs)
public FilterComparison(IReadOnlyList<string> path, FilterOperator @operator, object value, FilterValueType valueType)
{ {
Guard.NotNull(path, nameof(path)); Guard.NotNull(lhs, nameof(lhs));
Guard.NotEmpty(path, nameof(path)); Guard.NotEmpty(lhs, nameof(lhs));
Guard.Enum(@operator, nameof(@operator)); Guard.Enum(@operator, nameof(@operator));
Guard.Enum(valueType, nameof(valueType));
Path = path;
Value = value; Lhs = lhs;
ValueType = valueType; Rhs = rhs;
Operator = @operator; Operator = @operator;
} }
@ -41,28 +36,30 @@ namespace Squidex.Infrastructure.Queries
public override string ToString() public override string ToString()
{ {
var path = string.Join(".", Path); var path = string.Join(".", Lhs);
switch (Operator) switch (Operator)
{ {
case FilterOperator.Contains: case FilterOperator.Contains:
return $"contains({path}, {Value})"; return $"contains({path}, {Rhs})";
case FilterOperator.EndsWith: case FilterOperator.EndsWith:
return $"endsWith({path}, {Value})"; return $"endsWith({path}, {Rhs})";
case FilterOperator.StartsWith: case FilterOperator.StartsWith:
return $"startsWith({path}, {Value})"; return $"startsWith({path}, {Rhs})";
case FilterOperator.Equals: case FilterOperator.Equals:
return $"{path} == {Value}"; return $"{path} == {Rhs}";
case FilterOperator.NotEquals: case FilterOperator.NotEquals:
return $"{path} != {Value}"; return $"{path} != {Rhs}";
case FilterOperator.GreaterThan: case FilterOperator.GreaterThan:
return $"{path} > {Value}"; return $"{path} > {Rhs}";
case FilterOperator.GreaterThanOrEqual: case FilterOperator.GreaterThanOrEqual:
return $"{path} >= {Value}"; return $"{path} >= {Rhs}";
case FilterOperator.LessThan: case FilterOperator.LessThan:
return $"{path} < {Value}"; return $"{path} < {Rhs}";
case FilterOperator.LessThanOrEqual: case FilterOperator.LessThanOrEqual:
return $"{path} <= {Value}"; return $"{path} <= {Rhs}";
case FilterOperator.In:
return $"{path} in {Rhs}";
default: default:
return string.Empty; return string.Empty;
} }

1
src/Squidex.Infrastructure/Queries/FilterOperator.cs

@ -14,6 +14,7 @@ namespace Squidex.Infrastructure.Queries
Equals, Equals,
GreaterThan, GreaterThan,
GreaterThanOrEqual, GreaterThanOrEqual,
In,
LessThan, LessThan,
LessThanOrEqual, LessThanOrEqual,
NotEquals, NotEquals,

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

@ -0,0 +1,138 @@
// ==========================================================================
// 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.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>().ToArray())}]";
}
else if (Value == null)
{
return "<Null>";
}
else
{
return Value.ToString();
}
}
}
}

3
src/Squidex.Infrastructure/Queries/FilterValueType.cs

@ -16,6 +16,7 @@ namespace Squidex.Infrastructure.Queries
Int32, Int32,
Int64, Int64,
Single, Single,
String String,
Null
} }
} }

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

@ -6,6 +6,7 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Linq;
using Microsoft.OData; using Microsoft.OData;
using Microsoft.OData.Edm; using Microsoft.OData.Edm;
using Microsoft.OData.UriParser; using Microsoft.OData.UriParser;
@ -14,7 +15,7 @@ using NodaTime.Text;
namespace Squidex.Infrastructure.Queries.OData namespace Squidex.Infrastructure.Queries.OData
{ {
public sealed class ConstantWithTypeVisitor : QueryNodeVisitor<(object Value, FilterValueType ValueType)> public sealed class ConstantWithTypeVisitor : QueryNodeVisitor<FilterValue>
{ {
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);
@ -31,88 +32,142 @@ namespace Squidex.Infrastructure.Queries.OData
{ {
} }
public static (object Value, FilterValueType ValueType) Visit(QueryNode node) public static FilterValue Visit(QueryNode node)
{ {
return node.Accept(Instance); return node.Accept(Instance);
} }
public override (object Value, FilterValueType ValueType) Visit(ConvertNode nodeIn) public override FilterValue Visit(ConvertNode nodeIn)
{ {
if (nodeIn.TypeReference.Definition == BooleanType) if (nodeIn.TypeReference.Definition == BooleanType)
{ {
return (bool.Parse(ConstantVisitor.Visit(nodeIn.Source).ToString()), FilterValueType.Boolean); var value = ConstantVisitor.Visit(nodeIn.Source);
return new FilterValue(bool.Parse(value.ToString()));
} }
if (nodeIn.TypeReference.Definition == GuidType) if (nodeIn.TypeReference.Definition == GuidType)
{ {
return (Guid.Parse(ConstantVisitor.Visit(nodeIn.Source).ToString()), FilterValueType.Guid); var value = ConstantVisitor.Visit(nodeIn.Source);
return new FilterValue(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);
if (value is DateTimeOffset dateTimeOffset) return new FilterValue(ParseInstant(value));
{ }
return (Instant.FromDateTimeOffset(dateTimeOffset), FilterValueType.Instant);
} throw new NotSupportedException();
}
public override FilterValue Visit(CollectionConstantNode nodeIn)
{
if (nodeIn.ItemType.Definition == DateTimeType)
{
return new FilterValue(nodeIn.Collection.Select(x => ParseInstant(x.Value)).ToList());
}
if (nodeIn.ItemType.Definition == GuidType)
{
return new FilterValue(nodeIn.Collection.Select(x => (Guid)x.Value).ToList());
}
if (value is DateTime dateTime) if (nodeIn.ItemType.Definition == BooleanType)
{ {
return (Instant.FromDateTimeUtc(DateTime.SpecifyKind(dateTime, DateTimeKind.Utc)), FilterValueType.Instant); return new FilterValue(nodeIn.Collection.Select(x => (bool)x.Value).ToList());
} }
if (value is Date date) if (nodeIn.ItemType.Definition == SingleType)
{ {
return (Instant.FromUtc(date.Year, date.Month, date.Day, 0, 0), FilterValueType.Instant); return new FilterValue(nodeIn.Collection.Select(x => (float)x.Value).ToList());
} }
if (nodeIn.ItemType.Definition == DoubleType)
{
return new FilterValue(nodeIn.Collection.Select(x => (double)x.Value).ToList());
}
var parseResult = InstantPattern.General.Parse(Visit(nodeIn.Source).ToString()); if (nodeIn.ItemType.Definition == Int32Type)
{
return new FilterValue(nodeIn.Collection.Select(x => (int)x.Value).ToList());
}
if (!parseResult.Success) if (nodeIn.ItemType.Definition == Int64Type)
{ {
throw new ODataException("Datetime is not in a valid format. Use ISO 8601"); return new FilterValue(nodeIn.Collection.Select(x => (long)x.Value).ToList());
} }
return (parseResult.Value, FilterValueType.Instant); if (nodeIn.ItemType.Definition == StringType)
{
return new FilterValue(nodeIn.Collection.Select(x => (string)x.Value).ToList());
} }
return base.Visit(nodeIn); throw new NotSupportedException();
} }
public override (object Value, FilterValueType ValueType) Visit(ConstantNode nodeIn) public override FilterValue Visit(ConstantNode nodeIn)
{ {
if (nodeIn.TypeReference.Definition == BooleanType) if (nodeIn.TypeReference.Definition == BooleanType)
{ {
return (nodeIn.Value, FilterValueType.Boolean); return new FilterValue((bool)nodeIn.Value);
} }
if (nodeIn.TypeReference.Definition == SingleType) if (nodeIn.TypeReference.Definition == SingleType)
{ {
return (nodeIn.Value, FilterValueType.Single); return new FilterValue((float)nodeIn.Value);
} }
if (nodeIn.TypeReference.Definition == DoubleType) if (nodeIn.TypeReference.Definition == DoubleType)
{ {
return (nodeIn.Value, FilterValueType.Double); return new FilterValue((double)nodeIn.Value);
} }
if (nodeIn.TypeReference.Definition == Int32Type) if (nodeIn.TypeReference.Definition == Int32Type)
{ {
return (nodeIn.Value, FilterValueType.Int32); return new FilterValue((int)nodeIn.Value);
} }
if (nodeIn.TypeReference.Definition == Int64Type) if (nodeIn.TypeReference.Definition == Int64Type)
{ {
return (nodeIn.Value, FilterValueType.Int64); return new FilterValue((long)nodeIn.Value);
} }
if (nodeIn.TypeReference.Definition == StringType) if (nodeIn.TypeReference.Definition == StringType)
{ {
return (nodeIn.Value, FilterValueType.String); return new FilterValue((string)nodeIn.Value);
} }
throw new NotSupportedException(); throw new NotSupportedException();
} }
private Instant ParseInstant(object value)
{
if (value is DateTimeOffset dateTimeOffset)
{
return Instant.FromDateTimeOffset(dateTimeOffset.Add(dateTimeOffset.Offset));
}
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(value.ToString());
if (!parseResult.Success)
{
throw new ODataException("Datetime is not in a valid format. Use ISO 8601");
}
return parseResult.Value;
}
} }
} }

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

@ -39,6 +39,13 @@ namespace Squidex.Infrastructure.Queries.OData
throw new NotSupportedException(); throw new NotSupportedException();
} }
public override FilterNode Visit(InNode nodeIn)
{
var value = ConstantWithTypeVisitor.Visit(nodeIn.Right);
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.In, value);
}
public override FilterNode Visit(SingleValueFunctionCallNode nodeIn) public override FilterNode Visit(SingleValueFunctionCallNode nodeIn)
{ {
var fieldNode = nodeIn.Parameters.ElementAt(0); var fieldNode = nodeIn.Parameters.ElementAt(0);
@ -46,23 +53,23 @@ namespace Squidex.Infrastructure.Queries.OData
if (string.Equals(nodeIn.Name, "endswith", StringComparison.OrdinalIgnoreCase)) if (string.Equals(nodeIn.Name, "endswith", StringComparison.OrdinalIgnoreCase))
{ {
var (value, valueType) = ConstantWithTypeVisitor.Visit(valueNode); var value = ConstantWithTypeVisitor.Visit(valueNode);
return new FilterComparison(PropertyPathVisitor.Visit(fieldNode), FilterOperator.EndsWith, value, valueType); return new FilterComparison(PropertyPathVisitor.Visit(fieldNode), FilterOperator.EndsWith, value);
} }
if (string.Equals(nodeIn.Name, "startswith", StringComparison.OrdinalIgnoreCase)) if (string.Equals(nodeIn.Name, "startswith", StringComparison.OrdinalIgnoreCase))
{ {
var (value, valueType) = ConstantWithTypeVisitor.Visit(valueNode); var value = ConstantWithTypeVisitor.Visit(valueNode);
return new FilterComparison(PropertyPathVisitor.Visit(fieldNode), FilterOperator.StartsWith, value, valueType); return new FilterComparison(PropertyPathVisitor.Visit(fieldNode), FilterOperator.StartsWith, value);
} }
if (string.Equals(nodeIn.Name, "contains", StringComparison.OrdinalIgnoreCase)) if (string.Equals(nodeIn.Name, "contains", StringComparison.OrdinalIgnoreCase))
{ {
var (value, valueType) = ConstantWithTypeVisitor.Visit(valueNode); var value = ConstantWithTypeVisitor.Visit(valueNode);
return new FilterComparison(PropertyPathVisitor.Visit(fieldNode), FilterOperator.Contains, value, valueType); return new FilterComparison(PropertyPathVisitor.Visit(fieldNode), FilterOperator.Contains, value);
} }
throw new NotSupportedException(); throw new NotSupportedException();
@ -84,9 +91,9 @@ namespace Squidex.Infrastructure.Queries.OData
{ {
var regexFilter = Visit(functionNode); var regexFilter = Visit(functionNode);
var (value, _) = ConstantWithTypeVisitor.Visit(nodeIn.Right); var value = ConstantWithTypeVisitor.Visit(nodeIn.Right);
if (value is bool booleanRight) if (value.ValueType == FilterValueType.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))
@ -101,44 +108,44 @@ namespace Squidex.Infrastructure.Queries.OData
{ {
if (nodeIn.OperatorKind == BinaryOperatorKind.NotEqual) if (nodeIn.OperatorKind == BinaryOperatorKind.NotEqual)
{ {
var (value, valueType) = ConstantWithTypeVisitor.Visit(nodeIn.Right); var value = ConstantWithTypeVisitor.Visit(nodeIn.Right);
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.NotEquals, value, valueType); return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.NotEquals, value);
} }
if (nodeIn.OperatorKind == BinaryOperatorKind.Equal) if (nodeIn.OperatorKind == BinaryOperatorKind.Equal)
{ {
var (value, valueType) = ConstantWithTypeVisitor.Visit(nodeIn.Right); var value = ConstantWithTypeVisitor.Visit(nodeIn.Right);
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.Equals, value, valueType); return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.Equals, value);
} }
if (nodeIn.OperatorKind == BinaryOperatorKind.LessThan) if (nodeIn.OperatorKind == BinaryOperatorKind.LessThan)
{ {
var (value, valueType) = ConstantWithTypeVisitor.Visit(nodeIn.Right); var value = ConstantWithTypeVisitor.Visit(nodeIn.Right);
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.LessThan, value, valueType); return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.LessThan, value);
} }
if (nodeIn.OperatorKind == BinaryOperatorKind.LessThanOrEqual) if (nodeIn.OperatorKind == BinaryOperatorKind.LessThanOrEqual)
{ {
var (value, valueType) = ConstantWithTypeVisitor.Visit(nodeIn.Right); var value = ConstantWithTypeVisitor.Visit(nodeIn.Right);
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.LessThanOrEqual, value, valueType); return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.LessThanOrEqual, value);
} }
if (nodeIn.OperatorKind == BinaryOperatorKind.GreaterThan) if (nodeIn.OperatorKind == BinaryOperatorKind.GreaterThan)
{ {
var (value, valueType) = ConstantWithTypeVisitor.Visit(nodeIn.Right); var value = ConstantWithTypeVisitor.Visit(nodeIn.Right);
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.GreaterThan, value, valueType); return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.GreaterThan, value);
} }
if (nodeIn.OperatorKind == BinaryOperatorKind.GreaterThanOrEqual) if (nodeIn.OperatorKind == BinaryOperatorKind.GreaterThanOrEqual)
{ {
var (value, valueType) = ConstantWithTypeVisitor.Visit(nodeIn.Right); var value = ConstantWithTypeVisitor.Visit(nodeIn.Right);
return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.GreaterThanOrEqual, value, valueType); return new FilterComparison(PropertyPathVisitor.Visit(nodeIn.Left), FilterOperator.GreaterThanOrEqual, value);
} }
} }

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

@ -24,7 +24,7 @@ namespace Squidex.Infrastructure.Queries
public override FilterNode Visit(FilterComparison nodeIn) public override FilterNode Visit(FilterComparison nodeIn)
{ {
return new FilterComparison(nodeIn.Path.Select(x => x.ToPascalCase()).ToList(), nodeIn.Operator, nodeIn.Value, nodeIn.ValueType); return new FilterComparison(nodeIn.Lhs.Select(x => x.ToPascalCase()).ToList(), nodeIn.Operator, nodeIn.Rhs);
} }
} }
} }

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

@ -125,6 +125,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
Assert.Equal(o, i); Assert.Equal(o, i);
} }
[Fact]
public void Should_make_query_with_version_and_list()
{
var i = F(FilterBuilder.In("version", 0L, 2L, 5L));
var o = C("{ 'vs' : { '$in' : [NumberLong(0), NumberLong(2), NumberLong(5)] } }");
Assert.Equal(o, i);
}
[Fact] [Fact]
public void Should_make_query_from_draft() public void Should_make_query_from_draft()
{ {

72
tests/Squidex.Infrastructure.Tests/Queries/ODataConversionTests.cs

@ -58,6 +58,15 @@ namespace Squidex.Infrastructure.Queries
Assert.Equal(o, i); Assert.Equal(o, i);
} }
[Fact]
public void Should_parse_filter_when_type_is_datetime_list()
{
var i = Q("$filter=created in ('1988-01-19T12:00:00Z')");
var o = C("Filter: created in [1988-01-19T12:00:00Z]");
Assert.Equal(o, i);
}
[Fact] [Fact]
public void Should_parse_filter_when_type_is_date() public void Should_parse_filter_when_type_is_date()
{ {
@ -67,6 +76,15 @@ namespace Squidex.Infrastructure.Queries
Assert.Equal(o, i); Assert.Equal(o, i);
} }
[Fact]
public void Should_parse_filter_when_type_is_date_list()
{
var i = Q("$filter=created in ('1988-01-19')");
var o = C("Filter: created in [1988-01-19T00:00:00Z]");
Assert.Equal(o, i);
}
[Fact] [Fact]
public void Should_parse_filter_when_type_is_guid() public void Should_parse_filter_when_type_is_guid()
{ {
@ -76,6 +94,15 @@ namespace Squidex.Infrastructure.Queries
Assert.Equal(o, i); Assert.Equal(o, i);
} }
[Fact]
public void Should_parse_filter_when_type_is_guid_list()
{
var i = Q("$filter=id in ('B5FE25E3-B262-4B17-91EF-B3772A6B62BB')");
var o = C("Filter: id in [b5fe25e3-b262-4b17-91ef-b3772a6b62bb]");
Assert.Equal(o, i);
}
[Fact] [Fact]
public void Should_parse_filter_when_type_is_string() public void Should_parse_filter_when_type_is_string()
{ {
@ -85,6 +112,15 @@ namespace Squidex.Infrastructure.Queries
Assert.Equal(o, i); Assert.Equal(o, i);
} }
[Fact]
public void Should_parse_filter_when_type_is_string_list()
{
var i = Q("$filter=firstName in ('Dagobert')");
var o = C("Filter: firstName in [Dagobert]");
Assert.Equal(o, i);
}
[Fact] [Fact]
public void Should_parse_filter_when_type_is_boolean() public void Should_parse_filter_when_type_is_boolean()
{ {
@ -94,6 +130,15 @@ namespace Squidex.Infrastructure.Queries
Assert.Equal(o, i); Assert.Equal(o, i);
} }
[Fact]
public void Should_parse_filter_when_type_is_boolean_list()
{
var i = Q("$filter=isComicFigure in (true)");
var o = C("Filter: isComicFigure in [True]");
Assert.Equal(o, i);
}
[Fact] [Fact]
public void Should_parse_filter_when_type_is_int32() public void Should_parse_filter_when_type_is_int32()
{ {
@ -103,6 +148,15 @@ namespace Squidex.Infrastructure.Queries
Assert.Equal(o, i); Assert.Equal(o, i);
} }
[Fact]
public void Should_parse_filter_when_type_is_int32_list()
{
var i = Q("$filter=age in (60)");
var o = C("Filter: age in [60]");
Assert.Equal(o, i);
}
[Fact] [Fact]
public void Should_parse_filter_when_type_is_int64() public void Should_parse_filter_when_type_is_int64()
{ {
@ -112,6 +166,15 @@ namespace Squidex.Infrastructure.Queries
Assert.Equal(o, i); Assert.Equal(o, i);
} }
[Fact]
public void Should_parse_filter_when_type_is_int64_list()
{
var i = Q("$filter=incomeCents in (31543143513456789)");
var o = C("Filter: incomeCents in [31543143513456789]");
Assert.Equal(o, i);
}
[Fact] [Fact]
public void Should_parse_filter_when_type_is_double() public void Should_parse_filter_when_type_is_double()
{ {
@ -121,6 +184,15 @@ namespace Squidex.Infrastructure.Queries
Assert.Equal(o, i); Assert.Equal(o, i);
} }
[Fact]
public void Should_parse_filter_when_type_is_double_list()
{
var i = Q("$filter=incomeMio in (5634474356.1233)");
var o = C("Filter: incomeMio in [5634474356.1233]");
Assert.Equal(o, i);
}
[Fact] [Fact]
public void Should_parse_filter_with_negation() public void Should_parse_filter_with_negation()
{ {

Loading…
Cancel
Save