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..4e08cb3
--- /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().FirstOrDefault().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..0d8f3a0
--- /dev/null
+++ b/src/StringToExpression/GrammerDefinitions/CollectionDefinition.cs
@@ -0,0 +1,67 @@
+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.FirstOrDefault(p => p.Expression.Type == operandType).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..4444944 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,35 @@ 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|all)", ignore: true)
};
}
+ protected virtual IEnumerable CollectionDefinitions()
+ {
+ return new[]
+ {
+ new CollectionDefinition(
+ name: "ANY",
+ regex: @"any\(",
+ expressionBuilder: CreateCollectionAccess
+ ),
+ new CollectionDefinition(
+ name: "ALL",
+ regex: @"all\(",
+ 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..2d6640b 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("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("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("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) }), } },
+
+ 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("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("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("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("Toast", 4, new DateTime(2004, 01, 01), false, 10000000000, 111.111, 111.111f, 0xDD, 0.1m, guidArray[0],
+ Array.Empty()),
+ InstanceBuilders.BuildConcrete("Butter", 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..93adb14 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,49 @@ 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)")]
+ [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?
+ [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();
+
+ 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 +314,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