Browse Source

New exists operator.

pull/666/head
Sebastian 5 years ago
parent
commit
a6b1e6233a
  1. 1
      backend/i18n/frontend_en.json
  2. 1
      backend/i18n/frontend_it.json
  3. 1
      backend/i18n/frontend_nl.json
  4. 1
      backend/i18n/source/frontend_en.json
  5. 4
      backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterVisitor.cs
  6. 5
      backend/src/Squidex.Infrastructure/Queries/ClrFilter.cs
  7. 2
      backend/src/Squidex.Infrastructure/Queries/CompareFilter.cs
  8. 1
      backend/src/Squidex.Infrastructure/Queries/CompareOperator.cs
  9. 2
      backend/src/Squidex.Infrastructure/Queries/Json/JsonFilterSurrogate.cs
  10. 7
      backend/src/Squidex.Infrastructure/Queries/Json/OperatorValidator.cs
  11. 7
      backend/src/Squidex.Infrastructure/Queries/OData/EdmModelExtensions.cs
  12. 10
      backend/src/Squidex.Infrastructure/Queries/OData/FilterVisitor.cs
  13. 13
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/MongoDb/MongoDbQueryTests.cs
  14. 41
      backend/tests/Squidex.Infrastructure.Tests/Queries/QueryFromJsonTests.cs
  15. 27
      backend/tests/Squidex.Infrastructure.Tests/Queries/QueryFromODataTests.cs
  16. 3
      frontend/app/shared/state/query.ts

1
backend/i18n/frontend_en.json

@ -293,6 +293,7 @@
"common.queryOperators.empty": "is empty",
"common.queryOperators.endsWith": "ends with",
"common.queryOperators.eq": "is equals to",
"common.queryOperators.exists": "exists",
"common.queryOperators.ge": "is greater than or equals to",
"common.queryOperators.gt": "is greater than",
"common.queryOperators.le": "is less than pr equals to",

1
backend/i18n/frontend_it.json

@ -293,6 +293,7 @@
"common.queryOperators.empty": "è vuoto",
"common.queryOperators.endsWith": "finisce con",
"common.queryOperators.eq": "è uguale a",
"common.queryOperators.exists": "exists",
"common.queryOperators.ge": "è maggiore o uguale a",
"common.queryOperators.gt": "è maggiore di",
"common.queryOperators.le": "è minore o uguale a",

1
backend/i18n/frontend_nl.json

@ -293,6 +293,7 @@
"common.queryOperators.empty": "is leeg",
"common.queryOperators.endsWith": "eindigt op",
"common.queryOperators.eq": "is gelijk aan",
"common.queryOperators.exists": "exists",
"common.queryOperators.ge": "is groter dan of gelijk aan",
"common.queryOperators.gt": "is groter dan",
"common.queryOperators.le": "is kleiner dan of is gelijk aan",

1
backend/i18n/source/frontend_en.json

@ -293,6 +293,7 @@
"common.queryOperators.empty": "is empty",
"common.queryOperators.endsWith": "ends with",
"common.queryOperators.eq": "is equals to",
"common.queryOperators.exists": "exists",
"common.queryOperators.ge": "is greater than or equals to",
"common.queryOperators.gt": "is greater than",
"common.queryOperators.le": "is less than pr equals to",

4
backend/src/Squidex.Infrastructure.MongoDb/MongoDb/Queries/FilterVisitor.cs

@ -59,6 +59,10 @@ namespace Squidex.Infrastructure.MongoDb.Queries
Filter.Eq(propertyName, default(T)!),
Filter.Eq(propertyName, string.Empty),
Filter.Eq(propertyName, Array.Empty<T>()));
case CompareOperator.Exists:
return Filter.And(
Filter.Exists(propertyName, true),
Filter.Ne<object?>(propertyName, null));
case CompareOperator.StartsWith:
return Filter.Regex(propertyName, BuildRegex(nodeIn, s => "^" + s));
case CompareOperator.Contains:

5
backend/src/Squidex.Infrastructure/Queries/ClrFilter.cs

@ -86,6 +86,11 @@ namespace Squidex.Infrastructure.Queries
return Binary(path, CompareOperator.Empty, null);
}
public static CompareFilter<ClrValue> Exists(PropertyPath path)
{
return Binary(path, CompareOperator.Exists, null);
}
public static CompareFilter<ClrValue> In(PropertyPath path, ClrValue value)
{
return Binary(path, CompareOperator.In, value);

2
backend/src/Squidex.Infrastructure/Queries/CompareFilter.cs

@ -31,6 +31,8 @@ namespace Squidex.Infrastructure.Queries
return $"contains({Path}, {Value})";
case CompareOperator.Empty:
return $"empty({Path})";
case CompareOperator.Exists:
return $"exists({Path})";
case CompareOperator.EndsWith:
return $"endsWith({Path}, {Value})";
case CompareOperator.StartsWith:

1
backend/src/Squidex.Infrastructure/Queries/CompareOperator.cs

@ -11,6 +11,7 @@ namespace Squidex.Infrastructure.Queries
{
Contains,
Empty,
Exists,
EndsWith,
Equals,
GreaterThan,

2
backend/src/Squidex.Infrastructure/Queries/Json/JsonFilterSurrogate.cs

@ -75,6 +75,8 @@ namespace Squidex.Infrastructure.Queries
return CompareOperator.GreaterThanOrEqual;
case "empty":
return CompareOperator.Empty;
case "exists":
return CompareOperator.Exists;
case "contains":
return CompareOperator.Contains;
case "endswith":

7
backend/src/Squidex.Infrastructure/Queries/Json/OperatorValidator.cs

@ -16,12 +16,14 @@ namespace Squidex.Infrastructure.Queries.Json
private static readonly CompareOperator[] BooleanOperators =
{
CompareOperator.Equals,
CompareOperator.Exists,
CompareOperator.In,
CompareOperator.NotEquals
};
private static readonly CompareOperator[] NumberOperators =
{
CompareOperator.Equals,
CompareOperator.Exists,
CompareOperator.LessThan,
CompareOperator.LessThanOrEqual,
CompareOperator.GreaterThan,
@ -33,6 +35,7 @@ namespace Squidex.Infrastructure.Queries.Json
{
CompareOperator.Contains,
CompareOperator.Empty,
CompareOperator.Exists,
CompareOperator.EndsWith,
CompareOperator.Equals,
CompareOperator.GreaterThan,
@ -46,13 +49,15 @@ namespace Squidex.Infrastructure.Queries.Json
private static readonly CompareOperator[] ArrayOperators =
{
CompareOperator.Empty,
CompareOperator.Exists,
CompareOperator.Equals,
CompareOperator.In,
CompareOperator.NotEquals
};
private static readonly CompareOperator[] GeoOperators =
{
CompareOperator.LessThan
CompareOperator.LessThan,
CompareOperator.Exists,
};
public static bool IsAllowedOperator(JsonSchema schema, CompareOperator compareOperator)

7
backend/src/Squidex.Infrastructure/Queries/OData/EdmModelExtensions.cs

@ -19,7 +19,12 @@ namespace Squidex.Infrastructure.Queries.OData
CustomUriFunctions.AddCustomUriFunction("empty",
new FunctionSignatureWithReturnType(
EdmCoreModel.Instance.GetBoolean(false),
EdmCoreModel.Instance.GetString(true)));
EdmCoreModel.Instance.GetUntyped()));
CustomUriFunctions.AddCustomUriFunction("exists",
new FunctionSignatureWithReturnType(
EdmCoreModel.Instance.GetBoolean(false),
EdmCoreModel.Instance.GetUntyped()));
CustomUriFunctions.AddCustomUriFunction("distanceto",
new FunctionSignatureWithReturnType(

10
backend/src/Squidex.Infrastructure/Queries/OData/FilterVisitor.cs

@ -56,6 +56,16 @@ namespace Squidex.Infrastructure.Queries.OData
return ClrFilter.Empty(PropertyPathVisitor.Visit(fieldNode));
}
if (string.Equals(nodeIn.Name, "empty", StringComparison.OrdinalIgnoreCase))
{
return ClrFilter.Empty(PropertyPathVisitor.Visit(fieldNode));
}
if (string.Equals(nodeIn.Name, "exists", StringComparison.OrdinalIgnoreCase))
{
return ClrFilter.Exists(PropertyPathVisitor.Visit(fieldNode));
}
var valueNode = nodeIn.Parameters.ElementAt(1);
if (string.Equals(nodeIn.Name, "endswith", StringComparison.OrdinalIgnoreCase))

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

@ -202,8 +202,17 @@ namespace Squidex.Domain.Apps.Entities.Contents.MongoDb
[Fact]
public void Should_make_query_with_empty_test()
{
var i = _F(ClrFilter.Empty("data/firstName/iv"));
var o = _C("{ '$or' : [{ 'do.firstName.iv' : { '$exists' : false } }, { 'do.firstName.iv' : null }, { 'do.firstName.iv' : '' }, { 'do.firstName.iv' : [] }] }");
var i = _F(ClrFilter.Empty("id"));
var o = _C("{ '$or' : [{ '_id' : { '$exists' : false } }, { '_id' : null }, { '_id' : '' }, { '_id' : [] }] }");
Assert.Equal(o, i);
}
[Fact]
public void Should_make_query_with_exists_test()
{
var i = _F(ClrFilter.Exists("data/firstName/iv"));
var o = _C("{ 'do.firstName.iv' : { '$exists' : true, '$ne' : null } }");
Assert.Equal(o, i);
}

41
backend/tests/Squidex.Infrastructure.Tests/Queries/QueryFromJsonTests.cs

@ -24,6 +24,7 @@ namespace Squidex.Infrastructure.Queries
{
("Contains", "contains", "contains($FIELD, $VALUE)"),
("Empty", "empty", "empty($FIELD)"),
("Exists", "exists", "exists($FIELD)"),
("EndsWith", "endswith", "endsWith($FIELD, $VALUE)"),
("Equals", "eq", "$FIELD == $VALUE"),
("GreaterThanOrEqual", "ge", "$FIELD >= $VALUE"),
@ -241,25 +242,30 @@ namespace Squidex.Infrastructure.Queries
public class Geo
{
private static bool ValidOperator(string op)
{
return op == "lt" || op == "exists";
}
public static IEnumerable<object[]> ValidTests()
{
var value = new { longitude = 10, latitude = 20, distance = 30 };
return BuildFlatTests("geo", x => x == "lt", value, $"Radius({value.longitude}, {value.latitude}, {value.distance})");
return BuildFlatTests("geo", ValidOperator, value, $"Radius({value.longitude}, {value.latitude}, {value.distance})");
}
public static IEnumerable<object[]> ValidRefTests()
{
var value = new { longitude = 10, latitude = 20, distance = 30 };
return BuildFlatTests("geoRef", x => x == "lt", value, $"Radius({value.longitude}, {value.latitude}, {value.distance})");
return BuildFlatTests("geoRef", ValidOperator, value, $"Radius({value.longitude}, {value.latitude}, {value.distance})");
}
public static IEnumerable<object[]> InvalidTests()
{
var value = new { longitude = 10, latitude = 20, distance = 30 };
return BuildInvalidOperatorTests("geo", x => x == "lt", value);
return BuildInvalidOperatorTests("geo", ValidOperator, value);
}
[Theory]
@ -308,18 +314,23 @@ namespace Squidex.Infrastructure.Queries
public class Number
{
private static bool ValidOperator(string op)
{
return op.Length == 2 || op == "exists";
}
public static IEnumerable<object[]> ValidTests()
{
const int value = 12;
return BuildTests("number", x => x.Length == 2, value, $"{value}");
return BuildTests("number", ValidOperator, value, $"{value}");
}
public static IEnumerable<object[]> InvalidTests()
{
const int value = 12;
return BuildInvalidOperatorTests("number", x => x.Length == 2, $"{value}");
return BuildInvalidOperatorTests("number", ValidOperator, $"{value}");
}
public static IEnumerable<object[]> ValidInTests()
@ -367,18 +378,23 @@ namespace Squidex.Infrastructure.Queries
public class Boolean
{
private static bool ValidOperator(string op)
{
return op == "eq" || op == "ne" || op == "exists";
}
public static IEnumerable<object[]> ValidTests()
{
const bool value = true;
return BuildTests("boolean", x => x == "eq" || x == "ne", value, $"{value}");
return BuildTests("boolean", ValidOperator, value, $"{value}");
}
public static IEnumerable<object[]> InvalidTests()
{
const bool value = true;
return BuildInvalidOperatorTests("boolean", x => x == "eq" || x == "ne", value);
return BuildInvalidOperatorTests("boolean", ValidOperator, value);
}
public static IEnumerable<object[]> ValidInTests()
@ -426,11 +442,16 @@ namespace Squidex.Infrastructure.Queries
public class Array
{
public static IEnumerable<object[]> ValiedTests()
private static bool ValidOperator(string op)
{
return op == "eq" || op == "ne" || op == "empty" || op == "exists";
}
public static IEnumerable<object[]> ValidTests()
{
const string value = "Hello";
return BuildTests("stringArray", x => x == "eq" || x == "ne" || x == "empty", value, $"'{value}'");
return BuildTests("stringArray", ValidOperator, value, $"'{value}'");
}
public static IEnumerable<object[]> ValidInTests()
@ -441,7 +462,7 @@ namespace Squidex.Infrastructure.Queries
}
[Theory]
[MemberData(nameof(ValiedTests))]
[MemberData(nameof(ValidTests))]
public void Should_parse_array_filter(string field, string op, string value, string expected)
{
var json = new { path = field, op, value };

27
backend/tests/Squidex.Infrastructure.Tests/Queries/QueryFromODataTests.cs

@ -354,6 +354,33 @@ namespace Squidex.Infrastructure.Queries
Assert.Equal(o, i);
}
[Fact]
public void Should_parse_filter_with_exists()
{
var i = _Q("$filter=exists(lastName)");
var o = _C("Filter: exists(lastName)");
Assert.Equal(o, i);
}
[Fact]
public void Should_parse_filter_with_exists_to_true()
{
var i = _Q("$filter=exists(lastName) eq true");
var o = _C("Filter: exists(lastName)");
Assert.Equal(o, i);
}
[Fact]
public void Should_parse_filter_with_exists_to_false()
{
var i = _Q("$filter=exists(lastName) eq false");
var o = _C("Filter: !(exists(lastName))");
Assert.Equal(o, i);
}
[Fact]
public void Should_parse_filter_with_contains()
{

3
frontend/app/shared/state/query.ts

@ -231,7 +231,8 @@ const StringOperators: ReadonlyArray<FilterOperator> = [
];
const ArrayOperators: ReadonlyArray<FilterOperator> = [
{ name: 'i18n:common.queryOperators.empty', value: 'empty', noValue: true }
{ name: 'i18n:common.queryOperators.empty', value: 'empty', noValue: true },
{ name: 'i18n:common.queryOperators.exists', value: 'exists', noValue: true }
];
const TypeBoolean: QueryFieldModel = {

Loading…
Cancel
Save