From d36468ae4721fdd07c7a7f4d285d4eacb6c4901d Mon Sep 17 00:00:00 2001 From: Jan Jungblut Date: Fri, 21 Oct 2022 13:31:35 +0200 Subject: [PATCH 1/3] Simple any implementation --- ...ssorDefinitionDuplicatePatternException.cs | 24 +++++ .../GrammerDefinitions/CollectionAccessor.cs | 38 ++++++++ .../CollectionDefinition.cs | 66 +++++++++++++ .../GrammerDefinitions/OperandDefinition.cs | 5 +- src/StringToExpression/Language.cs | 7 +- .../Languages/ArithmeticLanguage.cs | 5 +- .../Languages/ODataFilterLanguage.cs | 81 +++++++++++++--- src/StringToExpression/Parser/Accessor.cs | 31 +++++++ src/StringToExpression/Parser/ParseState.cs | 5 +- src/StringToExpression/Parser/Parser.cs | 4 +- .../StringToExpression.csproj | 2 +- src/StringToExpression/Tokenizer/Token.cs | 2 + .../LinqToQuerystringTestDataFixture.cs | 92 ++++++++++++++----- .../LinqToQuerystringEquivalenceTests.cs | 26 ++++-- .../StringToExpression.Test.csproj | 2 +- 15 files changed, 333 insertions(+), 57 deletions(-) create mode 100644 src/StringToExpression/Exceptions/AccessorDefinitionDuplicatePatternException.cs create mode 100644 src/StringToExpression/GrammerDefinitions/CollectionAccessor.cs create mode 100644 src/StringToExpression/GrammerDefinitions/CollectionDefinition.cs create mode 100644 src/StringToExpression/Parser/Accessor.cs diff --git a/src/StringToExpression/Exceptions/AccessorDefinitionDuplicatePatternException.cs b/src/StringToExpression/Exceptions/AccessorDefinitionDuplicatePatternException.cs new file mode 100644 index 0000000..7e70803 --- /dev/null +++ b/src/StringToExpression/Exceptions/AccessorDefinitionDuplicatePatternException.cs @@ -0,0 +1,24 @@ +using System; + +namespace StringToExpression.Exceptions +{ + /// + /// Exception when a collection accessor pattern as been defined multiple times. + /// + public class AccessorDefinitionDuplicatePatternException : Exception + { + /// + /// The pattern that was duplicated + /// + public readonly string Pattern; + + /// + /// Initializes a new instance of the class. + /// + /// Pattern that has been duplicated. + public AccessorDefinitionDuplicatePatternException(string pattern) : base($"Pattern '{pattern}' has been defined multiple times") + { + Pattern = pattern; + } + } +} diff --git a/src/StringToExpression/GrammerDefinitions/CollectionAccessor.cs b/src/StringToExpression/GrammerDefinitions/CollectionAccessor.cs new file mode 100644 index 0000000..4f60dac --- /dev/null +++ b/src/StringToExpression/GrammerDefinitions/CollectionAccessor.cs @@ -0,0 +1,38 @@ +using StringToExpression.Exceptions; +using StringToExpression.Parser; +using StringToExpression.Tokenizer; +using System; +using System.Linq; +using System.Linq.Expressions; + +namespace StringToExpression.GrammerDefinitions +{ + public class CollectionAccessor : GrammerDefinition + { + public readonly Func ExpressionBuilder; + + + public CollectionAccessor(string name, string regex) + : base(name, regex) + { + + } + + public override void Apply(Token token, ParseState state) + { + var tokenValue = token.Value.Replace(":", ""); + var genType = state.Operands.ToArray().LastOrDefault().Expression.Type.GenericTypeArguments.FirstOrDefault(); + + var paramExpression = Expression.Parameter(genType, tokenValue); + + if (!state.Parameters.Select(p => p.Pattern).Contains(tokenValue)) + { + state.Parameters.Add(new Accessor(paramExpression, tokenValue)); + } + else + { + throw new AccessorDefinitionDuplicatePatternException(tokenValue); + } + } + } +} diff --git a/src/StringToExpression/GrammerDefinitions/CollectionDefinition.cs b/src/StringToExpression/GrammerDefinitions/CollectionDefinition.cs new file mode 100644 index 0000000..1a4da9b --- /dev/null +++ b/src/StringToExpression/GrammerDefinitions/CollectionDefinition.cs @@ -0,0 +1,66 @@ +using StringToExpression.Exceptions; +using StringToExpression.Parser; +using StringToExpression.Util; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace StringToExpression.GrammerDefinitions +{ + public class CollectionDefinition : BracketOpenDefinition + { + public readonly Func ExpressionBuilder; + + public CollectionDefinition(string name, string regex, Func expressionBuilder) + : this(name, regex, (v, sm, a) => expressionBuilder(v, sm)) + { + + } + + public CollectionDefinition(string name, string regex, Func expressionBuilder) + : base(name, regex) + { + ExpressionBuilder = expressionBuilder ?? throw new ArgumentNullException(nameof(expressionBuilder)); + } + + + /// + /// Applies the bracket operands. Executes the expressionBuilder with all the operands in the brackets. + /// + /// The operator that opened the bracket. + /// The list of operands within the brackets. + /// The operator that closed the bracket. + /// The current parse state. + /// When the number of opreands does not match the number of arguments + /// When argument Type does not match the type of the expression + /// When an error occured while executing the expressionBuilder + public override void ApplyBracketOperands(Operator bracketOpen, Stack bracketOperands, Operator bracketClose, ParseState state) + { + string methodName = ""; + var operandSource = StringSegment.Encompass(bracketOperands.Select(x => x.SourceMap)); + + switch (bracketOpen.SourceMap.Value) + { + case "all(": + methodName = nameof(Enumerable.All); + break; + case "any(": + methodName = nameof(Enumerable.Any); + break; + default: + throw new NotImplementedException($"{bracketOpen.SourceMap.Value} is an unknown collection definition"); + } + + var operandType = state.Operands.First().Expression.Type.GetGenericArguments()[0]; + + var methodCall = Expression.Call( + typeof(Enumerable), methodName, new[] { operandType }, + state.Operands.Pop().Expression, Expression.Lambda(bracketOperands.Pop().Expression, state.Parameters.LastOrDefault().Expression)); + + var functionSourceMap = StringSegment.Encompass(bracketOpen.SourceMap, operandSource); + + state.Operands.Push(new Operand(methodCall, functionSourceMap)); + } + } +} diff --git a/src/StringToExpression/GrammerDefinitions/OperandDefinition.cs b/src/StringToExpression/GrammerDefinitions/OperandDefinition.cs index e986e5a..0a19938 100644 --- a/src/StringToExpression/GrammerDefinitions/OperandDefinition.cs +++ b/src/StringToExpression/GrammerDefinitions/OperandDefinition.cs @@ -19,7 +19,7 @@ public class OperandDefinition : GrammerDefinition /// /// A function to generate the operator's Expression. /// - public readonly Func ExpressionBuilder; + public readonly Func ExpressionBuilder; /// /// Initializes a new instance of the class. @@ -41,7 +41,7 @@ public OperandDefinition(string name, string regex, Func exp /// The regex to match tokens. /// The function to generate the operator's Expression given the token's value and parsing state parameters. /// expressionBuilder - public OperandDefinition(string name, string regex, Func expressionBuilder) + public OperandDefinition(string name, string regex, Func expressionBuilder) : base(name, regex) { if (expressionBuilder == null) @@ -58,6 +58,7 @@ public OperandDefinition(string name, string regex, Func /// expression that represents the input string. /// - public Expression Parse(string text, params ParameterExpression[] parameters) + public Expression Parse(string text, params Accessor[] parameters) { var tokenStream = this.Tokenizer.Tokenize(text); var expression = Parser.Parse(tokenStream, parameters); @@ -76,10 +77,10 @@ public Expression> Parse(string text) /// public Expression> Parse(string text) { - var parameters = new[] { Expression.Parameter(typeof(TIn)) }; + var parameters = new[] { (Accessor)Expression.Parameter(typeof(TIn)) }; var body = this.Parse(text, parameters); - return Expression.Lambda>(body, parameters); + return Expression.Lambda>(body, parameters.Select(p => p.Expression)); } } } diff --git a/src/StringToExpression/Languages/ArithmeticLanguage.cs b/src/StringToExpression/Languages/ArithmeticLanguage.cs index c7ba990..d7e3022 100644 --- a/src/StringToExpression/Languages/ArithmeticLanguage.cs +++ b/src/StringToExpression/Languages/ArithmeticLanguage.cs @@ -1,4 +1,5 @@ using StringToExpression.GrammerDefinitions; +using StringToExpression.Parser; using StringToExpression.Util; using System; using System.Collections.Generic; @@ -45,10 +46,10 @@ public Expression> Parse(string text) /// public Expression> Parse(string text) { - var parameters = new[] { Expression.Parameter(typeof(T)) }; + var parameters = new[] { (Accessor)Expression.Parameter(typeof(T)) }; var body = language.Parse(text, parameters); body = ExpressionConversions.Convert(body, typeof(decimal)); - return Expression.Lambda>(body, parameters); + return Expression.Lambda>(body, parameters.Select(p => p.Expression)); } /// diff --git a/src/StringToExpression/Languages/ODataFilterLanguage.cs b/src/StringToExpression/Languages/ODataFilterLanguage.cs index f1533a4..e7cb12e 100644 --- a/src/StringToExpression/Languages/ODataFilterLanguage.cs +++ b/src/StringToExpression/Languages/ODataFilterLanguage.cs @@ -1,4 +1,5 @@ using StringToExpression.GrammerDefinitions; +using StringToExpression.Parser; using StringToExpression.Util; using System; using System.Collections.Generic; @@ -6,8 +7,6 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace StringToExpression.LanguageDefinitions { @@ -88,7 +87,8 @@ protected static class DateTimeMembers /// /// Initializes a new instance of the class. /// - public ODataFilterLanguage() { + public ODataFilterLanguage() + { language = new Language(AllDefinitions().ToArray()); } @@ -100,12 +100,12 @@ public ODataFilterLanguage() { /// public Expression> Parse(string text) { - var parameters = new[] { Expression.Parameter(typeof(T)) }; + var parameters = new[] { (Accessor)Expression.Parameter(typeof(T)) }; var body = language.Parse(text, parameters); ExpressionConversions.TryBoolean(ref body); - return Expression.Lambda>(body, parameters); + return Expression.Lambda>(body, parameters.Select(p => p.Expression)); } /// @@ -115,10 +115,13 @@ public Expression> Parse(string text) protected virtual IEnumerable AllDefinitions() { IEnumerable functions; + IEnumerable collections; + var definitions = new List(); definitions.AddRange(TypeDefinitions()); + definitions.AddRange(collections = CollectionDefinitions()); definitions.AddRange(functions = FunctionDefinitions()); - definitions.AddRange(BracketDefinitions(functions)); + definitions.AddRange(BracketDefinitions(functions, collections)); definitions.AddRange(LogicalOperatorDefinitions()); definitions.AddRange(ArithmeticOperatorDefinitions()); definitions.AddRange(PropertyDefinitions()); @@ -304,10 +307,11 @@ protected virtual IEnumerable ArithmeticOperatorDefinitions() /// /// The function calls in the language. (used as opening brackets) /// - protected virtual IEnumerable BracketDefinitions(IEnumerable functionCalls) + protected virtual IEnumerable BracketDefinitions(IEnumerable functionCalls, IEnumerable collectionCalls) { BracketOpenDefinition openBracket; ListDelimiterDefinition delimeter; + return new GrammerDefinition[] { openBracket = new BracketOpenDefinition( name: "OPEN_BRACKET", @@ -318,7 +322,7 @@ protected virtual IEnumerable BracketDefinitions(IEnumerable< new BracketCloseDefinition( name: "CLOSE_BRACKET", regex: @"\)", - bracketOpenDefinitions: new[] { openBracket }.Concat(functionCalls), + bracketOpenDefinitions: new[] { openBracket }.Concat(functionCalls).Concat(collectionCalls), listDelimeterDefinition: delimeter) }; } @@ -337,7 +341,7 @@ protected virtual IEnumerable FunctionDefinitions() argumentTypes: new[] {typeof(string), typeof(string) }, expressionBuilder: (parameters) => { return Expression.Call( - instance:parameters[0], + instance:parameters[0], method:StringMembers.StartsWith, arguments: new [] { parameters[1] }); }), @@ -443,18 +447,45 @@ protected virtual IEnumerable FunctionDefinitions() /// protected virtual IEnumerable PropertyDefinitions() { - return new[] + return new GrammerDefinition[] { //Properties + new CollectionAccessor( + name: "ACCESSOR", + regex:@"(?<=\()[a-z]+?:" + ), new OperandDefinition( name:"PROPERTY_PATH", - regex: @"(? { - return value.Split('/').Aggregate((Expression)parameters[0], (exp, prop)=> Expression.MakeMemberAccess(exp, TypeShim.GetProperty(exp.Type, prop))); - }), + regex: @"(? !string.IsNullOrWhiteSpace(p.Pattern) && p.Pattern.ToLower() == values[0].ToLower())) + { + parameter = parameters.FirstOrDefault(p => !string.IsNullOrWhiteSpace(p.Pattern) && p.Pattern.ToLower() == values[0].ToLower()); + values.RemoveAt(0); + } + else + { + parameter = parameters[0].Expression; + } + + return values.Aggregate(parameter, CreateMemberAccess); + } + + private static Expression CreateMemberAccess(Expression exp, string prop) + { + var memberAccess = Expression.MakeMemberAccess(exp, TypeShim.GetProperty(exp.Type, prop)); + return memberAccess; + } + /// /// Returns the definitions for whitespace used within the language. /// @@ -463,10 +494,30 @@ protected virtual IEnumerable WhitespaceDefinitions() { return new[] { - new GrammerDefinition(name: "WHITESPACE", regex: @"\s+", ignore: true) + new GrammerDefinition(name: "WHITESPACE", regex: @"\s+", ignore: true), + new GrammerDefinition(name: "SLASH", regex: @"\/(?=any)", ignore: true) }; } + protected virtual IEnumerable CollectionDefinitions() + { + return new[] + { + new CollectionDefinition( + name: "ANY", + regex: @"any\(", + expressionBuilder: CreateCollectionAccess + ) + }; + } + + private static Expression CreateCollectionAccess(string param, string source, ParameterExpression[] parameterExpressions) + { + //var expParam = Expression.Parameter(parameterExpressions[0], "sc"); + + return null; + } + /// /// Wraps the function to convert any constants to enums if required diff --git a/src/StringToExpression/Parser/Accessor.cs b/src/StringToExpression/Parser/Accessor.cs new file mode 100644 index 0000000..4517f14 --- /dev/null +++ b/src/StringToExpression/Parser/Accessor.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading.Tasks; + +namespace StringToExpression.Parser +{ + public class Accessor + { + public readonly ParameterExpression Expression; + public readonly string Pattern; + + public Accessor(ParameterExpression expression, string pattern = null) + { + Expression = expression; + Pattern = pattern; + } + + public static implicit operator Accessor(ParameterExpression expression) + { + return new Accessor(expression); + } + + public static implicit operator ParameterExpression(Accessor accessor) + { + return accessor.Expression; + } + } +} diff --git a/src/StringToExpression/Parser/ParseState.cs b/src/StringToExpression/Parser/ParseState.cs index d2b76cb..5032b1d 100644 --- a/src/StringToExpression/Parser/ParseState.cs +++ b/src/StringToExpression/Parser/ParseState.cs @@ -15,7 +15,7 @@ public class ParseState /// /// Gets the parameters that can be used during the parsing. /// - public List Parameters { get; } = new List(); + public List Parameters { get; } = new List(); /// /// Gets the current stack of operands. @@ -23,9 +23,8 @@ public class ParseState public Stack Operands { get; } = new Stack(); /// - /// Gets the current stack of operators. + /// Gets the current stack of operators. i.e. + - * / /// public Stack Operators { get; } = new Stack(); - } } diff --git a/src/StringToExpression/Parser/Parser.cs b/src/StringToExpression/Parser/Parser.cs index 50961da..18243ce 100644 --- a/src/StringToExpression/Parser/Parser.cs +++ b/src/StringToExpression/Parser/Parser.cs @@ -21,9 +21,9 @@ public class Parser /// The stream of tokens to parse. /// Any parameters that should be accessible by the operands and operators. /// An Expression that is the compiled state of all the tokens in the stream. - public Expression Parse(IEnumerable tokens, IEnumerable parameters = null) + public Expression Parse(IEnumerable tokens, IEnumerable parameters = null) { - parameters = parameters ?? Enumerable.Empty(); + parameters = parameters ?? Enumerable.Empty(); var compileState = new ParseState(); compileState.Parameters.AddRange(parameters); diff --git a/src/StringToExpression/StringToExpression.csproj b/src/StringToExpression/StringToExpression.csproj index 8eb2c68..e1f2d11 100644 --- a/src/StringToExpression/StringToExpression.csproj +++ b/src/StringToExpression/StringToExpression.csproj @@ -1,7 +1,7 @@  - netstandard1.0;net45 + net48 Codecutout Alex Davies StringToExpression supports the compiling a domain specific string into a .NET expression. diff --git a/src/StringToExpression/Tokenizer/Token.cs b/src/StringToExpression/Tokenizer/Token.cs index 17b24af..c2491a8 100644 --- a/src/StringToExpression/Tokenizer/Token.cs +++ b/src/StringToExpression/Tokenizer/Token.cs @@ -1,12 +1,14 @@ using StringToExpression.GrammerDefinitions; using StringToExpression.Util; using System; +using System.Diagnostics; namespace StringToExpression.Tokenizer { /// /// An indivdual piece of the complete input. /// + [DebuggerDisplay("{Value}")] public class Token { /// diff --git a/tests/StringToExpression.Test/Languages/ODataFilter/Fixtures/LinqToQuerystringTestDataFixture.cs b/tests/StringToExpression.Test/Languages/ODataFilter/Fixtures/LinqToQuerystringTestDataFixture.cs index a9a5fd5..a4a7494 100644 --- a/tests/StringToExpression.Test/Languages/ODataFilter/Fixtures/LinqToQuerystringTestDataFixture.cs +++ b/tests/StringToExpression.Test/Languages/ODataFilter/Fixtures/LinqToQuerystringTestDataFixture.cs @@ -9,16 +9,16 @@ namespace StringToExpression.Test.Fixtures public class LinqToQuerystringTestDataFixture { public readonly IQueryable ConcreteCollection; - + public readonly IQueryable ComplexCollection; - + public readonly IQueryable EdgeCaseCollection; - + public readonly IQueryable NullableCollection; public readonly IQueryable FunctionConcreteCollection; - public const string guid0 = "1C1D07FC-0446-4C7B-8DFA-643D42EED070" ; + public const string guid0 = "1C1D07FC-0446-4C7B-8DFA-643D42EED070"; public const string guid1 = "270FBCFF-8081-4ADC-B15F-FF9C979BB8AF"; public const string guid2 = "2F26B3D4-B730-4958-8036-1F7FB9DE20A9"; public const string guid3 = "2195EAF4-3692-4169-996E-A9B6F1560542"; @@ -37,17 +37,33 @@ public LinqToQuerystringTestDataFixture() ConcreteCollection = new List { - InstanceBuilders.BuildConcrete("Apple", 1, new DateTime(2002, 01, 01), true, 10000000000, 111.111, 111.111f, 0x00, 0.1m, guidArray[0]), - InstanceBuilders.BuildConcrete("Apple", 2, new DateTime(2005, 01, 01), false, 30000000000, 333.333, 333.333f, 0x22, 0.3m, guidArray[2]), - InstanceBuilders.BuildConcrete("Custard", 1, new DateTime(2003, 01, 01), true, 50000000000, 555.555, 555.555f, 0xDD, 0.5m, guidArray[4]), - InstanceBuilders.BuildConcrete("Custard", 2, new DateTime(2002, 01, 01), false, 30000000000, 333.333, 333.333f, 0x00, 0.3m, guidArray[2]), - InstanceBuilders.BuildConcrete("Custard", 3, new DateTime(2002, 01, 01), true, 40000000000, 444.444, 444.444f, 0x22, 0.4m, guidArray[3]), - InstanceBuilders.BuildConcrete("Banana", 3, new DateTime(2003, 01, 01), false, 10000000000, 111.111, 111.111f, 0x00, 0.1m, guidArray[0]), - InstanceBuilders.BuildConcrete("Eggs", 1, new DateTime(2005, 01, 01), true, 40000000000, 444.444, 444.444f, 0xCC, 0.4m, guidArray[3]), - InstanceBuilders.BuildConcrete("Eggs", 3, new DateTime(2001, 01, 01), false, 20000000000, 222.222, 222.222f, 0xCC, 0.2m, guidArray[1]), - InstanceBuilders.BuildConcrete("Dogfood", 4, new DateTime(2003, 01, 01), true, 30000000000, 333.333, 333.333f, 0xEE, 0.3m, guidArray[2]), - InstanceBuilders.BuildConcrete("Dogfood", 4, new DateTime(2004, 01, 01), false, 10000000000, 111.111, 111.111f, 0xDD, 0.1m, guidArray[0]), - InstanceBuilders.BuildConcrete("Dogfood", 5, new DateTime(2001, 01, 01), true, 20000000000, 222.222, 222.222f, 0xCC, 0.2m, guidArray[1]) + InstanceBuilders.BuildConcrete("Apple", 1, new DateTime(2002, 01, 01), true, 10000000000, 111.111, 111.111f, 0x00, 0.1m, guidArray[0], + new[] { InstanceBuilders.BuildEdgeCase("John Belushi", 33, new DateTime(1949, 1, 24), true) }), + InstanceBuilders.BuildConcrete("Apple", 2, new DateTime(2005, 01, 01), false, 30000000000, 333.333, 333.333f, 0x22, 0.3m, guidArray[2], + Array.Empty()), + InstanceBuilders.BuildConcrete("Custard", 1, new DateTime(2003, 01, 01), true, 50000000000, 555.555, 555.555f, 0xDD, 0.5m, guidArray[4], + new[] { InstanceBuilders.BuildEdgeCase("Scarlett Johansson", 31, new DateTime(1949, 1, 24), true), + InstanceBuilders.BuildEdgeCase("Chris Evans", 41, new DateTime(1949, 1, 24), true)}), + InstanceBuilders.BuildConcrete("Custard", 2, new DateTime(2002, 01, 01), false, 30000000000, 333.333, 333.333f, 0x00, 0.3m, guidArray[2], + new[] { InstanceBuilders.BuildEdgeCase("Mark Ruffalo", 43, new DateTime(1949, 1, 24), true), + InstanceBuilders.BuildEdgeCase("Chris Evans", 41, new DateTime(1949, 1, 24), true)}), + InstanceBuilders.BuildConcrete("Custard", 3, new DateTime(2002, 01, 01), true, 40000000000, 444.444, 444.444f, 0x22, 0.4m, guidArray[3], + new[] { InstanceBuilders.BuildEdgeCase("Chris Hemsworht", 37, new DateTime(1949, 1, 24), true), + InstanceBuilders.BuildEdgeCase("Scarlett Johansson", 31, new DateTime(1949, 1, 24), true) }), + InstanceBuilders.BuildConcrete("Banana", 3, new DateTime(2003, 01, 01), false, 10000000000, 111.111, 111.111f, 0x00, 0.1m, guidArray[0], + new[] { InstanceBuilders.BuildEdgeCase("Mark Ruffalo", 43, new DateTime(1949, 1, 24), true)} ), + InstanceBuilders.BuildConcrete("Eggs", 1, new DateTime(2005, 01, 01), true, 40000000000, 444.444, 444.444f, 0xCC, 0.4m, guidArray[3], + new[] { InstanceBuilders.BuildEdgeCase("John Belushi", 33, new DateTime(1949, 1, 24), true), + InstanceBuilders.BuildEdgeCase("Scarlett Johansson", 31, new DateTime(1949, 1, 24), true), + InstanceBuilders.BuildEdgeCase("Chris Hemsworht", 37, new DateTime(1949, 1, 24), true) }), + InstanceBuilders.BuildConcrete("Eggs", 3, new DateTime(2001, 01, 01), false, 20000000000, 222.222, 222.222f, 0xCC, 0.2m, guidArray[1], + new [] { InstanceBuilders.BuildEdgeCase("Tom Holland", 27, new DateTime(1992, 12, 12), false)} ), + InstanceBuilders.BuildConcrete("Dogfood", 4, new DateTime(2003, 01, 01), true, 30000000000, 333.333, 333.333f, 0xEE, 0.3m, guidArray[2], + new [] { InstanceBuilders.BuildEdgeCase("Jeremy Renner", 40, new DateTime(1992, 12, 12), false)}), + InstanceBuilders.BuildConcrete("Dogfood", 4, new DateTime(2004, 01, 01), false, 10000000000, 111.111, 111.111f, 0xDD, 0.1m, guidArray[0], + Array.Empty()), + InstanceBuilders.BuildConcrete("Dogfood", 5, new DateTime(2001, 01, 01), true, 20000000000, 222.222, 222.222f, 0xCC, 0.2m, guidArray[1], + Array.Empty()) }.AsQueryable(); FunctionConcreteCollection = new List @@ -77,11 +93,40 @@ public LinqToQuerystringTestDataFixture() ComplexCollection = new List { - new ComplexClass { Title = "Charles", Concrete = InstanceBuilders.BuildConcrete("Apple", 5, new DateTime(2005, 01, 01), true) }, - new ComplexClass { Title = "Andrew", Concrete = InstanceBuilders.BuildConcrete("Custard", 3, new DateTime(2007, 01, 01), true) }, - new ComplexClass { Title = "David", Concrete = InstanceBuilders.BuildConcrete("Banana", 2, new DateTime(2003, 01, 01), false) }, - new ComplexClass { Title = "Edward", Concrete = InstanceBuilders.BuildConcrete("Eggs", 1, new DateTime(2000, 01, 01), true) }, - new ComplexClass { Title = "Boris", Concrete = InstanceBuilders.BuildConcrete("Dogfood", 4, new DateTime(2009, 01, 01), false) } + new ComplexClass { Title = "Charles", Concrete = InstanceBuilders.BuildConcrete("Apple", 5, new DateTime(2005, 01, 01), true), StringCollection = new List() { "Paul", "Leonardo", "Emilia" }, + ConcreteCollection = new List { InstanceBuilders.BuildConcrete("Apple", 5, new DateTime(2002, 01, 01), true, 10000000000, 111.111, 111.111f, 0x00, 0.1m, guidArray[0], + new[] { InstanceBuilders.BuildEdgeCase("John Belushi", 33, new DateTime(1949, 1, 24), true) }), + InstanceBuilders.BuildConcrete("Apple", 2, new DateTime(2005, 01, 01), false, 30000000000, 333.333, 333.333f, 0x22, 0.3m, guidArray[2], + Array.Empty()) } }, + + new ComplexClass { Title = "Andrew", Concrete = InstanceBuilders.BuildConcrete("Custard", 3, new DateTime(2007, 01, 01), true), StringCollection = new List() { "John", "Johnny", "Emilia" }, + ConcreteCollection = new List { InstanceBuilders.BuildConcrete("Custard", 1, new DateTime(2003, 01, 01), true, 50000000000, 555.555, 555.555f, 0xDD, 0.5m, guidArray[4], + new[] { InstanceBuilders.BuildEdgeCase("Scarlett Johansson", 31, new DateTime(1949, 1, 24), true), + InstanceBuilders.BuildEdgeCase("Chris Evans", 41, new DateTime(1949, 1, 24), true)}), + InstanceBuilders.BuildConcrete("Custard", 4, new DateTime(2002, 01, 01), false, 30000000000, 333.333, 333.333f, 0x00, 0.3m, guidArray[2], + new[] { InstanceBuilders.BuildEdgeCase("Mark Ruffalo", 43, new DateTime(1949, 1, 24), true), + InstanceBuilders.BuildEdgeCase("Chris Evans", 41, new DateTime(1949, 1, 24), true)}), + InstanceBuilders.BuildConcrete("Custard", 3, new DateTime(2002, 01, 01), true, 40000000000, 444.444, 444.444f, 0x22, 0.4m, guidArray[3], + new[] { InstanceBuilders.BuildEdgeCase("Chris Hemsworht", 37, new DateTime(1949, 1, 24), true), + InstanceBuilders.BuildEdgeCase("Scarlett Johansson", 31, new DateTime(1949, 1, 24), true) }), } }, + + new ComplexClass { Title = "David", Concrete = InstanceBuilders.BuildConcrete("Banana", 2, new DateTime(2003, 01, 01), false), StringCollection = new List() { "Brad", "Angelina" }, + ConcreteCollection = new List { InstanceBuilders.BuildConcrete("Banana", 3, new DateTime(2003, 01, 01), false, 10000000000, 111.111, 111.111f, 0x00, 0.1m, guidArray[0], + new[] { InstanceBuilders.BuildEdgeCase("Mark Ruffalo", 43, new DateTime(1949, 1, 24), true) }), } }, + new ComplexClass { Title = "Edward", Concrete = InstanceBuilders.BuildConcrete("Eggs", 1, new DateTime(2000, 01, 01), true), StringCollection = new List() { "John", "Christopher", "Emma" }, + ConcreteCollection = new List { InstanceBuilders.BuildConcrete("Eggs", 1, new DateTime(2005, 01, 01), true, 40000000000, 444.444, 444.444f, 0xCC, 0.4m, guidArray[3], + new[] { InstanceBuilders.BuildEdgeCase("John Belushi", 33, new DateTime(1949, 1, 24), true), + InstanceBuilders.BuildEdgeCase("Scarlett Johansson", 31, new DateTime(1949, 1, 24), true), + InstanceBuilders.BuildEdgeCase("Chris Hemsworht", 37, new DateTime(1949, 1, 24), true) }), + InstanceBuilders.BuildConcrete("Eggs", 3, new DateTime(2001, 01, 01), false, 20000000000, 222.222, 222.222f, 0xCC, 0.2m, guidArray[1], + new [] { InstanceBuilders.BuildEdgeCase("Tom Holland", 27, new DateTime(1992, 12, 12), false)} ) } }, + new ComplexClass { Title = "Boris", Concrete = InstanceBuilders.BuildConcrete("Dogfood", 4, new DateTime(2009, 01, 01), false), StringCollection = new List() { "John", "Clint", "Helen" }, + ConcreteCollection = new List { InstanceBuilders.BuildConcrete("Dogfood", 4, new DateTime(2003, 01, 01), true, 30000000000, 333.333, 333.333f, 0xEE, 0.3m, guidArray[2], + new [] { InstanceBuilders.BuildEdgeCase("Jeremy Renner", 40, new DateTime(1992, 12, 12), false)}), + InstanceBuilders.BuildConcrete("Dogfood", 4, new DateTime(2004, 01, 01), false, 10000000000, 111.111, 111.111f, 0xDD, 0.1m, guidArray[0], + Array.Empty()), + InstanceBuilders.BuildConcrete("Dogfood", 5, new DateTime(2001, 01, 01), true, 20000000000, 222.222, 222.222f, 0xCC, 0.2m, guidArray[1], + Array.Empty()) } } }.AsQueryable(); NullableCollection = new List @@ -108,6 +153,11 @@ public static ConcreteClass BuildConcrete(string name, int age, DateTime date, b return new ConcreteClass { Name = name, Date = date, Age = age, Complete = complete, Population = population, Value = value, Cost = cost, Code = code, Score = score, Guid = guid }; } + public static ConcreteClass BuildConcrete(string name, int age, DateTime date, bool complete, long population, double value, float cost, byte code, decimal score, Guid guid, EdgeCaseClass[] children) + { + return new ConcreteClass { Name = name, Date = date, Age = age, Complete = complete, Population = population, Value = value, Cost = cost, Code = code, Score = score, Guid = guid, Children = children.ToList() }; + } + public static NullableClass BuildNull() { return new NullableClass(); @@ -265,7 +315,5 @@ public class NullableClassDto { public IEnumerable NullableCollection { get; set; } } - } - } diff --git a/tests/StringToExpression.Test/Languages/ODataFilter/LinqToQuerystringEquivalenceTests.cs b/tests/StringToExpression.Test/Languages/ODataFilter/LinqToQuerystringEquivalenceTests.cs index 0b9c301..63ea217 100644 --- a/tests/StringToExpression.Test/Languages/ODataFilter/LinqToQuerystringEquivalenceTests.cs +++ b/tests/StringToExpression.Test/Languages/ODataFilter/LinqToQuerystringEquivalenceTests.cs @@ -165,7 +165,7 @@ public void When_concrete_data_should_return_same_results_as_linqToQuerystring(s [InlineData(@"Name eq 'Apple\rBob'")] [InlineData(@"Name eq 'Apple""Bob'")] [InlineData(@"Name eq 'Apple\'Bob'")] - + public void When_edgecase_data_should_return_same_results_as_linqToQuerystring(string query) { var linqToQuerystringFiltered = Data.EdgeCaseCollection.LinqToQuerystring("?$filter=" + query).ToList(); @@ -218,7 +218,7 @@ public void When_edgecase_data_should_return_same_results_as_linqToQuerystring(s [InlineData("Name ne null")] [InlineData("null ne Name")] public void When_nullable_data_should_return_same_results_as_linqToQuerystring(string query) - { + { var linqToQuerystringFiltered = Data.NullableCollection.LinqToQuerystring("?$filter=" + query).ToList(); var filter = new ODataFilterLanguage().Parse(query); @@ -264,25 +264,39 @@ public void When_property_paths_results_as_linqToQuerystring(string query) Assert.Equal(linqToQuerystringFiltered, stringParserFiltered); } + [Theory] + [InlineData("ConcreteCollection/any(c: c/Age gt 4)")] + [InlineData("ConcreteCollection/any(c: c/Age lt 3)")] + [InlineData("ConcreteCollection/any(c: c/Age ge 4)")] + public void When_collection_filter_results_as_linqToQuerystring(string query) + { + var lingqToQuerystringFiltered = Data.ComplexCollection.LinqToQuerystring("?$filter=" + query).ToList(); + + var filter = new ODataFilterLanguage().Parse(query); + var stringParserFiltered = Data.ComplexCollection.Where(filter).ToList(); + + Assert.Equal(lingqToQuerystringFiltered, stringParserFiltered); + } + [Theory] [InlineData(@"Id eq null")] [InlineData(@"Id eq 'somestring'")] [InlineData(@"Id eq Name")] public void When_invalid_checks_should_error(string query) { - var linqToQuerystringException = Assert.ThrowsAny(()=>Data.ConcreteCollection.LinqToQuerystring("?$filter=" + query).ToList()); + var linqToQuerystringException = Assert.ThrowsAny(() => Data.ConcreteCollection.LinqToQuerystring("?$filter=" + query).ToList()); var stringToExprssionException = Assert.Throws(() => new ODataFilterLanguage().Parse(query)); } - [Fact(Skip ="Performance sanity check.")] + [Fact(Skip = "Performance sanity check.")] public void Should_be_faster_than_linqToQuerystring() { var baseDatetime = new DateTime(2003, 01, 01); var linqToQueryStringStopwatch = new Stopwatch(); linqToQueryStringStopwatch.Start(); - for(int i = 0; i < 10000; i++) + for (int i = 0; i < 10000; i++) { var date = baseDatetime.AddDays(i).ToString("s"); var linqToQuerystringFiltered = Data.ConcreteCollection.LinqToQuerystring($"?$filter=Name eq 'Apple' and (Complete eq true or Date gt datetime'{date}')"); @@ -290,7 +304,7 @@ public void Should_be_faster_than_linqToQuerystring() linqToQueryStringStopwatch.Stop(); - + var parseStringStopwatch = new Stopwatch(); parseStringStopwatch.Start(); var language = new ODataFilterLanguage(); diff --git a/tests/StringToExpression.Test/StringToExpression.Test.csproj b/tests/StringToExpression.Test/StringToExpression.Test.csproj index ac2a4c2..75d8b18 100644 --- a/tests/StringToExpression.Test/StringToExpression.Test.csproj +++ b/tests/StringToExpression.Test/StringToExpression.Test.csproj @@ -1,7 +1,7 @@  - net46 + net48 From 78a72673ab4a46c608a4ec23eca4f5e022ee2b82 Mon Sep 17 00:00:00 2001 From: Jan Jungblut Date: Fri, 21 Oct 2022 16:02:51 +0200 Subject: [PATCH 2/3] Nested any --- .../GrammerDefinitions/CollectionAccessor.cs | 2 +- .../GrammerDefinitions/CollectionDefinition.cs | 5 +++-- .../Fixtures/LinqToQuerystringTestDataFixture.cs | 16 ++++++++-------- .../LinqToQuerystringEquivalenceTests.cs | 9 ++++++++- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/StringToExpression/GrammerDefinitions/CollectionAccessor.cs b/src/StringToExpression/GrammerDefinitions/CollectionAccessor.cs index 4f60dac..4e08cb3 100644 --- a/src/StringToExpression/GrammerDefinitions/CollectionAccessor.cs +++ b/src/StringToExpression/GrammerDefinitions/CollectionAccessor.cs @@ -21,7 +21,7 @@ public CollectionAccessor(string name, string regex) public override void Apply(Token token, ParseState state) { var tokenValue = token.Value.Replace(":", ""); - var genType = state.Operands.ToArray().LastOrDefault().Expression.Type.GenericTypeArguments.FirstOrDefault(); + var genType = state.Operands.ToArray().FirstOrDefault().Expression.Type.GenericTypeArguments.FirstOrDefault(); var paramExpression = Expression.Parameter(genType, tokenValue); diff --git a/src/StringToExpression/GrammerDefinitions/CollectionDefinition.cs b/src/StringToExpression/GrammerDefinitions/CollectionDefinition.cs index 1a4da9b..0d8f3a0 100644 --- a/src/StringToExpression/GrammerDefinitions/CollectionDefinition.cs +++ b/src/StringToExpression/GrammerDefinitions/CollectionDefinition.cs @@ -47,7 +47,7 @@ public override void ApplyBracketOperands(Operator bracketOpen, Stack b break; case "any(": methodName = nameof(Enumerable.Any); - break; + break; default: throw new NotImplementedException($"{bracketOpen.SourceMap.Value} is an unknown collection definition"); } @@ -56,7 +56,8 @@ public override void ApplyBracketOperands(Operator bracketOpen, Stack b var methodCall = Expression.Call( typeof(Enumerable), methodName, new[] { operandType }, - state.Operands.Pop().Expression, Expression.Lambda(bracketOperands.Pop().Expression, state.Parameters.LastOrDefault().Expression)); + state.Operands.Pop().Expression, + Expression.Lambda(bracketOperands.Pop().Expression, state.Parameters.FirstOrDefault(p => p.Expression.Type == operandType).Expression)); var functionSourceMap = StringSegment.Encompass(bracketOpen.SourceMap, operandSource); diff --git a/tests/StringToExpression.Test/Languages/ODataFilter/Fixtures/LinqToQuerystringTestDataFixture.cs b/tests/StringToExpression.Test/Languages/ODataFilter/Fixtures/LinqToQuerystringTestDataFixture.cs index a4a7494..2d6640b 100644 --- a/tests/StringToExpression.Test/Languages/ODataFilter/Fixtures/LinqToQuerystringTestDataFixture.cs +++ b/tests/StringToExpression.Test/Languages/ODataFilter/Fixtures/LinqToQuerystringTestDataFixture.cs @@ -96,17 +96,17 @@ public LinqToQuerystringTestDataFixture() new ComplexClass { Title = "Charles", Concrete = InstanceBuilders.BuildConcrete("Apple", 5, new DateTime(2005, 01, 01), true), StringCollection = new List() { "Paul", "Leonardo", "Emilia" }, ConcreteCollection = new List { InstanceBuilders.BuildConcrete("Apple", 5, new DateTime(2002, 01, 01), true, 10000000000, 111.111, 111.111f, 0x00, 0.1m, guidArray[0], new[] { InstanceBuilders.BuildEdgeCase("John Belushi", 33, new DateTime(1949, 1, 24), true) }), - InstanceBuilders.BuildConcrete("Apple", 2, new DateTime(2005, 01, 01), false, 30000000000, 333.333, 333.333f, 0x22, 0.3m, guidArray[2], + InstanceBuilders.BuildConcrete("Potato", 2, new DateTime(2005, 01, 01), false, 30000000000, 333.333, 333.333f, 0x22, 0.3m, guidArray[2], Array.Empty()) } }, new ComplexClass { Title = "Andrew", Concrete = InstanceBuilders.BuildConcrete("Custard", 3, new DateTime(2007, 01, 01), true), StringCollection = new List() { "John", "Johnny", "Emilia" }, ConcreteCollection = new List { InstanceBuilders.BuildConcrete("Custard", 1, new DateTime(2003, 01, 01), true, 50000000000, 555.555, 555.555f, 0xDD, 0.5m, guidArray[4], new[] { InstanceBuilders.BuildEdgeCase("Scarlett Johansson", 31, new DateTime(1949, 1, 24), true), InstanceBuilders.BuildEdgeCase("Chris Evans", 41, new DateTime(1949, 1, 24), true)}), - InstanceBuilders.BuildConcrete("Custard", 4, new DateTime(2002, 01, 01), false, 30000000000, 333.333, 333.333f, 0x00, 0.3m, guidArray[2], + InstanceBuilders.BuildConcrete("Pear", 4, new DateTime(2002, 01, 01), false, 30000000000, 333.333, 333.333f, 0x00, 0.3m, guidArray[2], new[] { InstanceBuilders.BuildEdgeCase("Mark Ruffalo", 43, new DateTime(1949, 1, 24), true), InstanceBuilders.BuildEdgeCase("Chris Evans", 41, new DateTime(1949, 1, 24), true)}), - InstanceBuilders.BuildConcrete("Custard", 3, new DateTime(2002, 01, 01), true, 40000000000, 444.444, 444.444f, 0x22, 0.4m, guidArray[3], + InstanceBuilders.BuildConcrete("Peach", 3, new DateTime(2002, 01, 01), true, 40000000000, 444.444, 444.444f, 0x22, 0.4m, guidArray[3], new[] { InstanceBuilders.BuildEdgeCase("Chris Hemsworht", 37, new DateTime(1949, 1, 24), true), InstanceBuilders.BuildEdgeCase("Scarlett Johansson", 31, new DateTime(1949, 1, 24), true) }), } }, @@ -114,18 +114,18 @@ public LinqToQuerystringTestDataFixture() ConcreteCollection = new List { InstanceBuilders.BuildConcrete("Banana", 3, new DateTime(2003, 01, 01), false, 10000000000, 111.111, 111.111f, 0x00, 0.1m, guidArray[0], new[] { InstanceBuilders.BuildEdgeCase("Mark Ruffalo", 43, new DateTime(1949, 1, 24), true) }), } }, new ComplexClass { Title = "Edward", Concrete = InstanceBuilders.BuildConcrete("Eggs", 1, new DateTime(2000, 01, 01), true), StringCollection = new List() { "John", "Christopher", "Emma" }, - ConcreteCollection = new List { InstanceBuilders.BuildConcrete("Eggs", 1, new DateTime(2005, 01, 01), true, 40000000000, 444.444, 444.444f, 0xCC, 0.4m, guidArray[3], + ConcreteCollection = new List { InstanceBuilders.BuildConcrete("Bacon", 1, new DateTime(2005, 01, 01), true, 40000000000, 444.444, 444.444f, 0xCC, 0.4m, guidArray[3], new[] { InstanceBuilders.BuildEdgeCase("John Belushi", 33, new DateTime(1949, 1, 24), true), InstanceBuilders.BuildEdgeCase("Scarlett Johansson", 31, new DateTime(1949, 1, 24), true), InstanceBuilders.BuildEdgeCase("Chris Hemsworht", 37, new DateTime(1949, 1, 24), true) }), - InstanceBuilders.BuildConcrete("Eggs", 3, new DateTime(2001, 01, 01), false, 20000000000, 222.222, 222.222f, 0xCC, 0.2m, guidArray[1], + InstanceBuilders.BuildConcrete("Tea", 3, new DateTime(2001, 01, 01), false, 20000000000, 222.222, 222.222f, 0xCC, 0.2m, guidArray[1], new [] { InstanceBuilders.BuildEdgeCase("Tom Holland", 27, new DateTime(1992, 12, 12), false)} ) } }, new ComplexClass { Title = "Boris", Concrete = InstanceBuilders.BuildConcrete("Dogfood", 4, new DateTime(2009, 01, 01), false), StringCollection = new List() { "John", "Clint", "Helen" }, - ConcreteCollection = new List { InstanceBuilders.BuildConcrete("Dogfood", 4, new DateTime(2003, 01, 01), true, 30000000000, 333.333, 333.333f, 0xEE, 0.3m, guidArray[2], + ConcreteCollection = new List { InstanceBuilders.BuildConcrete("Ketchup", 4, new DateTime(2003, 01, 01), true, 30000000000, 333.333, 333.333f, 0xEE, 0.3m, guidArray[2], new [] { InstanceBuilders.BuildEdgeCase("Jeremy Renner", 40, new DateTime(1992, 12, 12), false)}), - InstanceBuilders.BuildConcrete("Dogfood", 4, new DateTime(2004, 01, 01), false, 10000000000, 111.111, 111.111f, 0xDD, 0.1m, guidArray[0], + InstanceBuilders.BuildConcrete("Toast", 4, new DateTime(2004, 01, 01), false, 10000000000, 111.111, 111.111f, 0xDD, 0.1m, guidArray[0], Array.Empty()), - InstanceBuilders.BuildConcrete("Dogfood", 5, new DateTime(2001, 01, 01), true, 20000000000, 222.222, 222.222f, 0xCC, 0.2m, guidArray[1], + InstanceBuilders.BuildConcrete("Butter", 5, new DateTime(2001, 01, 01), true, 20000000000, 222.222, 222.222f, 0xCC, 0.2m, guidArray[1], Array.Empty()) } } }.AsQueryable(); diff --git a/tests/StringToExpression.Test/Languages/ODataFilter/LinqToQuerystringEquivalenceTests.cs b/tests/StringToExpression.Test/Languages/ODataFilter/LinqToQuerystringEquivalenceTests.cs index 63ea217..3344e13 100644 --- a/tests/StringToExpression.Test/Languages/ODataFilter/LinqToQuerystringEquivalenceTests.cs +++ b/tests/StringToExpression.Test/Languages/ODataFilter/LinqToQuerystringEquivalenceTests.cs @@ -267,7 +267,14 @@ public void When_property_paths_results_as_linqToQuerystring(string query) [Theory] [InlineData("ConcreteCollection/any(c: c/Age gt 4)")] [InlineData("ConcreteCollection/any(c: c/Age lt 3)")] - [InlineData("ConcreteCollection/any(c: c/Age ge 4)")] + [InlineData("ConcreteCollection/any(c: c/Age ge 4)")] + [InlineData("ConcreteCollection/any(c: c/Age ge 4) and Title eq 'Charles'")] + [InlineData("ConcreteCollection/any(c: c/Age lt 3 and c/Name eq 'Apple')")] + [InlineData("ConcreteCollection/any(c: c/Children/any(ci: ci/Age gt 41))")] + [InlineData("ConcreteCollection/any(c: c/Children/any(ci: ci/Age gt 41 and ci/Name eq 'Mark Ruffalo'))")] + [InlineData("ConcreteCollection/any(c: c/Children/any(ci: ci/Age gt 41) and c/Name eq 'Banana')")] + [InlineData("ConcreteCollection/any(c: c/Children/any(ci: ci/Age gt 41)) and Title eq 'David'")] + [InlineData("ConcreteCollection/any(c: c/Name eq Concrete/Name)")] // I think linqToQuery is wrong here? public void When_collection_filter_results_as_linqToQuerystring(string query) { var lingqToQuerystringFiltered = Data.ComplexCollection.LinqToQuerystring("?$filter=" + query).ToList(); From 0a6393ceb7be9e543fbaf861951b18d90bcc198f Mon Sep 17 00:00:00 2001 From: Jan Jungblut Date: Mon, 24 Oct 2022 14:23:16 +0200 Subject: [PATCH 3/3] All operator --- src/StringToExpression/Languages/ODataFilterLanguage.cs | 9 +++++++-- .../ODataFilter/LinqToQuerystringEquivalenceTests.cs | 5 ++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/StringToExpression/Languages/ODataFilterLanguage.cs b/src/StringToExpression/Languages/ODataFilterLanguage.cs index e7cb12e..4444944 100644 --- a/src/StringToExpression/Languages/ODataFilterLanguage.cs +++ b/src/StringToExpression/Languages/ODataFilterLanguage.cs @@ -456,7 +456,7 @@ protected virtual IEnumerable PropertyDefinitions() ), new OperandDefinition( name:"PROPERTY_PATH", - regex: @"(? WhitespaceDefinitions() return new[] { new GrammerDefinition(name: "WHITESPACE", regex: @"\s+", ignore: true), - new GrammerDefinition(name: "SLASH", regex: @"\/(?=any)", ignore: true) + new GrammerDefinition(name: "SLASH", regex: @"\/(?=any|all)", ignore: true) }; } @@ -507,6 +507,11 @@ protected virtual IEnumerable CollectionDefinitions() name: "ANY", regex: @"any\(", expressionBuilder: CreateCollectionAccess + ), + new CollectionDefinition( + name: "ALL", + regex: @"all\(", + expressionBuilder: CreateCollectionAccess ) }; } diff --git a/tests/StringToExpression.Test/Languages/ODataFilter/LinqToQuerystringEquivalenceTests.cs b/tests/StringToExpression.Test/Languages/ODataFilter/LinqToQuerystringEquivalenceTests.cs index 3344e13..93adb14 100644 --- a/tests/StringToExpression.Test/Languages/ODataFilter/LinqToQuerystringEquivalenceTests.cs +++ b/tests/StringToExpression.Test/Languages/ODataFilter/LinqToQuerystringEquivalenceTests.cs @@ -274,7 +274,10 @@ public void When_property_paths_results_as_linqToQuerystring(string query) [InlineData("ConcreteCollection/any(c: c/Children/any(ci: ci/Age gt 41 and ci/Name eq 'Mark Ruffalo'))")] [InlineData("ConcreteCollection/any(c: c/Children/any(ci: ci/Age gt 41) and c/Name eq 'Banana')")] [InlineData("ConcreteCollection/any(c: c/Children/any(ci: ci/Age gt 41)) and Title eq 'David'")] - [InlineData("ConcreteCollection/any(c: c/Name eq Concrete/Name)")] // I think linqToQuery is wrong here? + //[InlineData("ConcreteCollection/any(c: c/Name eq Concrete/Name)")] // I think linqToQuery is wrong here? + [InlineData("ConcreteCollection/all(c: c/Age ge 3)")] + [InlineData("ConcreteCollection/all(c: c/Age ge 3) and StringCollection/any(sc: sc eq 'Brad')")] + [InlineData("ConcreteCollection/all(c: c/Age ge 3) and Title eq 'Boris'")] public void When_collection_filter_results_as_linqToQuerystring(string query) { var lingqToQuerystringFiltered = Data.ComplexCollection.LinqToQuerystring("?$filter=" + query).ToList();