From ef4ad8a72541a289082690587f6cca635acde0f6 Mon Sep 17 00:00:00 2001 From: Jamess-Lucass <23193271+Jamess-Lucass@users.noreply.github.com> Date: Thu, 25 Jul 2024 22:15:44 +0100 Subject: [PATCH 1/2] wip --- example/Dto/UserDto.cs | 1 + example/Entities/User.cs | 1 + example/Program.cs | 3 +- src/GoatQuery/src/Ast/StringLiteral.cs | 10 +++ .../src/Evaluator/FilterEvaluator.cs | 85 ++++++++++++++++++- src/GoatQuery/src/Lexer/Lexer.cs | 10 ++- src/GoatQuery/src/Parser/Parser.cs | 8 +- src/GoatQuery/src/Token/Token.cs | 1 + src/GoatQuery/tests/Filter/FilterLexerTest.cs | 22 +++++ .../tests/Filter/FilterParserTest.cs | 1 + src/GoatQuery/tests/Filter/FilterTest.cs | 17 ++-- src/GoatQuery/tests/User.cs | 1 + 12 files changed, 147 insertions(+), 13 deletions(-) diff --git a/example/Dto/UserDto.cs b/example/Dto/UserDto.cs index 1cd1837..84d71e6 100644 --- a/example/Dto/UserDto.cs +++ b/example/Dto/UserDto.cs @@ -8,4 +8,5 @@ public record UserDto public string Firstname { get; set; } = string.Empty; public string Lastname { get; set; } = string.Empty; public int Age { get; set; } + public double Test { get; set; } } \ No newline at end of file diff --git a/example/Entities/User.cs b/example/Entities/User.cs index 479f6cb..e1ef673 100644 --- a/example/Entities/User.cs +++ b/example/Entities/User.cs @@ -5,4 +5,5 @@ public record User public string Lastname { get; set; } = string.Empty; public int Age { get; set; } public bool IsDeleted { get; set; } + public double Test { get; set; } } \ No newline at end of file diff --git a/example/Program.cs b/example/Program.cs index e58704a..ab69851 100644 --- a/example/Program.cs +++ b/example/Program.cs @@ -37,7 +37,8 @@ .RuleFor(x => x.Firstname, f => f.Person.FirstName) .RuleFor(x => x.Lastname, f => f.Person.LastName) .RuleFor(x => x.Age, f => f.Random.Int(0, 100)) - .RuleFor(x => x.IsDeleted, f => f.Random.Bool()); + .RuleFor(x => x.IsDeleted, f => f.Random.Bool()) + .RuleFor(x => x.Test, f => f.Random.Double()); context.Users.AddRange(users.Generate(1_000)); context.SaveChanges(); diff --git a/src/GoatQuery/src/Ast/StringLiteral.cs b/src/GoatQuery/src/Ast/StringLiteral.cs index 112e320..c31fd64 100644 --- a/src/GoatQuery/src/Ast/StringLiteral.cs +++ b/src/GoatQuery/src/Ast/StringLiteral.cs @@ -28,4 +28,14 @@ public IntegerLiteral(Token token, int value) : base(token) { Value = value; } +} + +public sealed class DecimalLiteral : QueryExpression +{ + public decimal Value { get; set; } + + public DecimalLiteral(Token token, decimal value) : base(token) + { + Value = value; + } } \ No newline at end of file diff --git a/src/GoatQuery/src/Evaluator/FilterEvaluator.cs b/src/GoatQuery/src/Evaluator/FilterEvaluator.cs index bc6140d..9d20b7a 100644 --- a/src/GoatQuery/src/Evaluator/FilterEvaluator.cs +++ b/src/GoatQuery/src/Evaluator/FilterEvaluator.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq.Expressions; using FluentResults; @@ -18,7 +19,7 @@ public static Result Evaluate(QueryExpression expression, ParameterE var property = Expression.Property(parameterExpression, propertyName); - ConstantExpression value = null; + ConstantExpression value; switch (exp.Right) { @@ -26,13 +27,28 @@ public static Result Evaluate(QueryExpression expression, ParameterE value = Expression.Constant(literal.Value, property.Type); break; case IntegerLiteral literal: - value = Expression.Constant(literal.Value, property.Type); + var integerConstant = GetIntegerExpressionConstant(literal.Value, property.Type); + if (integerConstant.IsFailed) + { + return Result.Fail(integerConstant.Errors); + } + + value = integerConstant.Value; + break; + case DecimalLiteral literal: + var decimalConstant = GetDecimalExpressionConstant(literal.Value, property.Type); + if (decimalConstant.IsFailed) + { + return Result.Fail(decimalConstant.Errors); + } + + value = decimalConstant.Value; break; case StringLiteral literal: value = Expression.Constant(literal.Value, property.Type); break; default: - break; + return Result.Fail($"Unsupported literal type: {exp.Right.GetType().Name}"); } switch (exp.Operator) @@ -47,6 +63,8 @@ public static Result Evaluate(QueryExpression expression, ParameterE var method = identifier.Value.GetType().GetMethod("Contains", new[] { value?.Value.GetType() }); return Expression.Call(property, method, value); + default: + return Result.Fail($"Unsupported operator: {exp.Operator}"); } } @@ -75,4 +93,65 @@ public static Result Evaluate(QueryExpression expression, ParameterE return null; } + + private static Result GetIntegerExpressionConstant(int value, Type targetType) + { + try + { + // Fetch the underlying type if it's nullable. + var underlyingType = Nullable.GetUnderlyingType(targetType); + var type = underlyingType ?? targetType; + + object convertedValue = type switch + { + Type t when t == typeof(int) => value, + Type t when t == typeof(long) => Convert.ToInt64(value), + Type t when t == typeof(short) => Convert.ToInt16(value), + Type t when t == typeof(byte) => Convert.ToByte(value), + Type t when t == typeof(uint) => Convert.ToUInt32(value), + Type t when t == typeof(ulong) => Convert.ToUInt64(value), + Type t when t == typeof(ushort) => Convert.ToUInt16(value), + Type t when t == typeof(sbyte) => Convert.ToSByte(value), + _ => throw new NotSupportedException($"Unsupported numeric type: {targetType.Name}") + }; + + return Expression.Constant(convertedValue, targetType); + } + catch (OverflowException) + { + return Result.Fail($"Value {value} is too large for type {targetType.Name}"); + } + catch (Exception ex) + { + return Result.Fail($"Error converting {value} to {targetType.Name}: {ex.Message}"); + } + } + + private static Result GetDecimalExpressionConstant(decimal value, Type targetType) + { + try + { + // Fetch the underlying type if it's nullable. + var underlyingType = Nullable.GetUnderlyingType(targetType); + var type = underlyingType ?? targetType; + + object convertedValue = type switch + { + Type t when t == typeof(decimal) => value, + Type t when t == typeof(float) => Convert.ToSingle(value), + Type t when t == typeof(double) => Convert.ToDouble(value), + _ => throw new NotSupportedException($"Unsupported numeric type: {targetType.Name}") + }; + + return Expression.Constant(convertedValue, targetType); + } + catch (OverflowException) + { + return Result.Fail($"Value {value} is too large for type {targetType.Name}"); + } + catch (Exception ex) + { + return Result.Fail($"Error converting {value} to {targetType.Name}: {ex.Message}"); + } + } } \ No newline at end of file diff --git a/src/GoatQuery/src/Lexer/Lexer.cs b/src/GoatQuery/src/Lexer/Lexer.cs index 8ff184c..66cb55d 100644 --- a/src/GoatQuery/src/Lexer/Lexer.cs +++ b/src/GoatQuery/src/Lexer/Lexer.cs @@ -61,6 +61,12 @@ public Token NextToken() return token; } + if (IsDigit(token.Literal[0]) && token.Literal.Contains(".")) + { + token.Type = TokenType.DECIMAL; + return token; + } + if (IsDigit(token.Literal[0])) { token.Type = TokenType.INT; @@ -87,7 +93,7 @@ private string ReadIdentifier() { var currentPosition = _position; - while (IsLetter(_character) || IsDigit(_character)) + while (IsLetter(_character) || IsDigit(_character) || _character == '-' || _character == '.') { ReadCharacter(); } @@ -97,7 +103,7 @@ private string ReadIdentifier() private bool IsLetter(char ch) { - return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch == '-'; + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'; } private bool IsDigit(char ch) diff --git a/src/GoatQuery/src/Parser/Parser.cs b/src/GoatQuery/src/Parser/Parser.cs index 815790c..7006390 100644 --- a/src/GoatQuery/src/Parser/Parser.cs +++ b/src/GoatQuery/src/Parser/Parser.cs @@ -139,7 +139,7 @@ private Result ParseFilterStatement() var statement = new InfixExpression(_currentToken, identifier, _currentToken.Literal); - if (!PeekTokenIn(TokenType.STRING, TokenType.INT, TokenType.GUID)) + if (!PeekTokenIn(TokenType.STRING, TokenType.INT, TokenType.GUID, TokenType.DECIMAL)) { return Result.Fail("Invalid value type within filter"); } @@ -168,6 +168,12 @@ private Result ParseFilterStatement() statement.Right = new IntegerLiteral(_currentToken, intValue); } break; + case TokenType.DECIMAL: + if (decimal.TryParse(_currentToken.Literal, out var decimalValue)) + { + statement.Right = new DecimalLiteral(_currentToken, decimalValue); + } + break; } return statement; diff --git a/src/GoatQuery/src/Token/Token.cs b/src/GoatQuery/src/Token/Token.cs index accbd29..9cd496f 100644 --- a/src/GoatQuery/src/Token/Token.cs +++ b/src/GoatQuery/src/Token/Token.cs @@ -5,6 +5,7 @@ public enum TokenType IDENT, STRING, INT, + DECIMAL, GUID, LPAREN, RPAREN, diff --git a/src/GoatQuery/tests/Filter/FilterLexerTest.cs b/src/GoatQuery/tests/Filter/FilterLexerTest.cs index bd38f03..bd8b117 100644 --- a/src/GoatQuery/tests/Filter/FilterLexerTest.cs +++ b/src/GoatQuery/tests/Filter/FilterLexerTest.cs @@ -180,6 +180,28 @@ public static IEnumerable Parameters() new (TokenType.GUID, "e4c7772b-8947-4e46-98ed-644b417d2a08"), } }; + + yield return new object[] + { + "id eq 10.50", + new KeyValuePair[] + { + new (TokenType.IDENT, "id"), + new (TokenType.IDENT, "eq"), + new (TokenType.DECIMAL, "10.50"), + } + }; + + yield return new object[] + { + "id ne 0.1121563052701180", + new KeyValuePair[] + { + new (TokenType.IDENT, "id"), + new (TokenType.IDENT, "ne"), + new (TokenType.DECIMAL, "0.1121563052701180"), + } + }; } [Theory] diff --git a/src/GoatQuery/tests/Filter/FilterParserTest.cs b/src/GoatQuery/tests/Filter/FilterParserTest.cs index 4ecefa3..b3c6af3 100644 --- a/src/GoatQuery/tests/Filter/FilterParserTest.cs +++ b/src/GoatQuery/tests/Filter/FilterParserTest.cs @@ -9,6 +9,7 @@ public sealed class FilterParserTest [InlineData("Age ne 10", "Age", "ne", "10")] [InlineData("Name contains 'John'", "Name", "contains", "John")] [InlineData("Id eq e4c7772b-8947-4e46-98ed-644b417d2a08", "Id", "eq", "e4c7772b-8947-4e46-98ed-644b417d2a08")] + [InlineData("Id eq 0.1121563052701180", "Id", "eq", "0.1121563052701180")] public void Test_ParsingFilterStatement(string input, string expectedLeft, string expectedOperator, string expectedRight) { var lexer = new QueryLexer(input); diff --git a/src/GoatQuery/tests/Filter/FilterTest.cs b/src/GoatQuery/tests/Filter/FilterTest.cs index a143be6..30b399f 100644 --- a/src/GoatQuery/tests/Filter/FilterTest.cs +++ b/src/GoatQuery/tests/Filter/FilterTest.cs @@ -4,12 +4,12 @@ public sealed class FilterTest { private static readonly Dictionary _users = new Dictionary { - ["John"] = new User { Age = 2, Firstname = "John", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255") }, - ["Jane"] = new User { Age = 1, Firstname = "Jane", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255") }, - ["Apple"] = new User { Age = 2, Firstname = "Apple", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255") }, - ["Harry"] = new User { Age = 1, Firstname = "Harry", UserId = Guid.Parse("e4c7772b-8947-4e46-98ed-644b417d2a08") }, - ["Doe"] = new User { Age = 3, Firstname = "Doe", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255") }, - ["Egg"] = new User { Age = 3, Firstname = "Egg", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255") }, + ["John"] = new User { Age = 2, Firstname = "John", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), Balance = 1.50m }, + ["Jane"] = new User { Age = 1, Firstname = "Jane", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), Balance = 0 }, + ["Apple"] = new User { Age = 2, Firstname = "Apple", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), Balance = 1204050.98m }, + ["Harry"] = new User { Age = 1, Firstname = "Harry", UserId = Guid.Parse("e4c7772b-8947-4e46-98ed-644b417d2a08"), Balance = 0.5372958205929493m }, + ["Doe"] = new User { Age = 3, Firstname = "Doe", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), Balance = null }, + ["Egg"] = new User { Age = 3, Firstname = "Egg", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), Balance = 1334534453453433.33435443343231235652m }, }; public static IEnumerable Parameters() @@ -103,6 +103,11 @@ public static IEnumerable Parameters() "UserId eq e4c7772b-8947-4e46-98ed-644b417d2a08", new[] { _users["Harry"] } }; + + yield return new object[] { + "balance eq 0.5372958205929493", + new[] { _users["Harry"] } + }; } [Theory] diff --git a/src/GoatQuery/tests/User.cs b/src/GoatQuery/tests/User.cs index dcf3b0d..5c815c7 100644 --- a/src/GoatQuery/tests/User.cs +++ b/src/GoatQuery/tests/User.cs @@ -5,6 +5,7 @@ public record User public int Age { get; set; } public Guid UserId { get; set; } public string Firstname { get; set; } = string.Empty; + public decimal? Balance { get; set; } } public sealed record CustomJsonPropertyUser : User From 66f348f1f864b4f1a90d3b0c4615065abab45d49 Mon Sep 17 00:00:00 2001 From: Jamess-Lucass <23193271+Jamess-Lucass@users.noreply.github.com> Date: Wed, 31 Jul 2024 23:25:47 +0100 Subject: [PATCH 2/2] added decimal --- src/GoatQuery/src/Ast/StringLiteral.cs | 20 +++++ .../src/Evaluator/FilterEvaluator.cs | 42 ++------- src/GoatQuery/src/Lexer/Lexer.cs | 24 ++++-- src/GoatQuery/src/Parser/Parser.cs | 24 +++++- src/GoatQuery/src/Token/Token.cs | 2 + src/GoatQuery/tests/Filter/FilterLexerTest.cs | 85 ++++++++++++++++++- .../tests/Filter/FilterParserTest.cs | 4 +- src/GoatQuery/tests/Filter/FilterTest.cs | 52 +++++++++--- src/GoatQuery/tests/User.cs | 4 +- 9 files changed, 196 insertions(+), 61 deletions(-) diff --git a/src/GoatQuery/src/Ast/StringLiteral.cs b/src/GoatQuery/src/Ast/StringLiteral.cs index 7a45ea4..e5b38c2 100644 --- a/src/GoatQuery/src/Ast/StringLiteral.cs +++ b/src/GoatQuery/src/Ast/StringLiteral.cs @@ -40,6 +40,26 @@ public DecimalLiteral(Token token, decimal value) : base(token) } } +public sealed class FloatLiteral : QueryExpression +{ + public float Value { get; set; } + + public FloatLiteral(Token token, float value) : base(token) + { + Value = value; + } +} + +public sealed class DoubleLiteral : QueryExpression +{ + public double Value { get; set; } + + public DoubleLiteral(Token token, double value) : base(token) + { + Value = value; + } +} + public sealed class DateTimeLiteral : QueryExpression { public DateTime Value { get; set; } diff --git a/src/GoatQuery/src/Evaluator/FilterEvaluator.cs b/src/GoatQuery/src/Evaluator/FilterEvaluator.cs index a1744a1..e9df1b4 100644 --- a/src/GoatQuery/src/Evaluator/FilterEvaluator.cs +++ b/src/GoatQuery/src/Evaluator/FilterEvaluator.cs @@ -37,13 +37,13 @@ public static Result Evaluate(QueryExpression expression, ParameterE value = integerConstant.Value; break; case DecimalLiteral literal: - var decimalConstant = GetDecimalExpressionConstant(literal.Value, property.Type); - if (decimalConstant.IsFailed) - { - return Result.Fail(decimalConstant.Errors); - } - - value = decimalConstant.Value; + value = Expression.Constant(literal.Value, property.Type); + break; + case FloatLiteral literal: + value = Expression.Constant(literal.Value, property.Type); + break; + case DoubleLiteral literal: + value = Expression.Constant(literal.Value, property.Type); break; case StringLiteral literal: value = Expression.Constant(literal.Value, property.Type); @@ -138,32 +138,4 @@ private static Result GetIntegerExpressionConstant(int value return Result.Fail($"Error converting {value} to {targetType.Name}: {ex.Message}"); } } - - private static Result GetDecimalExpressionConstant(decimal value, Type targetType) - { - try - { - // Fetch the underlying type if it's nullable. - var underlyingType = Nullable.GetUnderlyingType(targetType); - var type = underlyingType ?? targetType; - - object convertedValue = type switch - { - Type t when t == typeof(decimal) => value, - Type t when t == typeof(float) => Convert.ToSingle(value), - Type t when t == typeof(double) => Convert.ToDouble(value), - _ => throw new NotSupportedException($"Unsupported numeric type: {targetType.Name}") - }; - - return Expression.Constant(convertedValue, targetType); - } - catch (OverflowException) - { - return Result.Fail($"Value {value} is too large for type {targetType.Name}"); - } - catch (Exception ex) - { - return Result.Fail($"Error converting {value} to {targetType.Name}: {ex.Message}"); - } - } } \ No newline at end of file diff --git a/src/GoatQuery/src/Lexer/Lexer.cs b/src/GoatQuery/src/Lexer/Lexer.cs index d560751..65b3ba3 100644 --- a/src/GoatQuery/src/Lexer/Lexer.cs +++ b/src/GoatQuery/src/Lexer/Lexer.cs @@ -62,12 +62,6 @@ public Token NextToken() return token; } - if (IsDigit(token.Literal[0]) && token.Literal.Contains(".")) - { - token.Type = TokenType.DECIMAL; - return token; - } - if (IsDigit(token.Literal[0])) { if (IsDateTime(token.Literal)) @@ -76,6 +70,24 @@ public Token NextToken() return token; } + if (token.Literal.EndsWith("f", StringComparison.OrdinalIgnoreCase)) + { + token.Type = TokenType.FLOAT; + return token; + } + + if (token.Literal.EndsWith("m", StringComparison.OrdinalIgnoreCase)) + { + token.Type = TokenType.DECIMAL; + return token; + } + + if (token.Literal.EndsWith("d", StringComparison.OrdinalIgnoreCase)) + { + token.Type = TokenType.DOUBLE; + return token; + } + token.Type = TokenType.INT; return token; } diff --git a/src/GoatQuery/src/Parser/Parser.cs b/src/GoatQuery/src/Parser/Parser.cs index 5456d3e..745b336 100644 --- a/src/GoatQuery/src/Parser/Parser.cs +++ b/src/GoatQuery/src/Parser/Parser.cs @@ -140,7 +140,7 @@ private Result ParseFilterStatement() var statement = new InfixExpression(_currentToken, identifier, _currentToken.Literal); - if (!PeekTokenIn(TokenType.STRING, TokenType.INT, TokenType.GUID, TokenType.DATETIME, TokenType.DECIMAL)) + if (!PeekTokenIn(TokenType.STRING, TokenType.INT, TokenType.GUID, TokenType.DATETIME, TokenType.DECIMAL, TokenType.FLOAT, TokenType.DOUBLE)) { return Result.Fail("Invalid value type within filter"); } @@ -152,7 +152,7 @@ private Result ParseFilterStatement() return Result.Fail("Value must be a string when using 'contains' operand"); } - if (statement.Operator.In(Keywords.Lt, Keywords.Lte, Keywords.Gt, Keywords.Gte) && !CurrentTokenIn(TokenType.INT, TokenType.DATETIME)) + if (statement.Operator.In(Keywords.Lt, Keywords.Lte, Keywords.Gt, Keywords.Gte) && !CurrentTokenIn(TokenType.INT, TokenType.DECIMAL, TokenType.FLOAT, TokenType.DOUBLE, TokenType.DATETIME)) { return Result.Fail($"Value must be an integer when using '{statement.Operator}' operand"); } @@ -174,12 +174,30 @@ private Result ParseFilterStatement() statement.Right = new IntegerLiteral(_currentToken, intValue); } break; + case TokenType.FLOAT: + var floatValueWithoutSuffixLiteral = _currentToken.Literal.TrimEnd('f'); + + if (float.TryParse(floatValueWithoutSuffixLiteral, out var floatValue)) + { + statement.Right = new FloatLiteral(_currentToken, floatValue); + } + break; case TokenType.DECIMAL: - if (decimal.TryParse(_currentToken.Literal, out var decimalValue)) + var decimalValueWithoutSuffixLiteral = _currentToken.Literal.TrimEnd('m'); + + if (decimal.TryParse(decimalValueWithoutSuffixLiteral, out var decimalValue)) { statement.Right = new DecimalLiteral(_currentToken, decimalValue); } break; + case TokenType.DOUBLE: + var doubleValueWithoutSuffixLiteral = _currentToken.Literal.TrimEnd('d'); + + if (double.TryParse(doubleValueWithoutSuffixLiteral, out var doubleValue)) + { + statement.Right = new DoubleLiteral(_currentToken, doubleValue); + } + break; case TokenType.DATETIME: if (DateTime.TryParse(_currentToken.Literal, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var dateTimeValue)) { diff --git a/src/GoatQuery/src/Token/Token.cs b/src/GoatQuery/src/Token/Token.cs index 77382ef..058b681 100644 --- a/src/GoatQuery/src/Token/Token.cs +++ b/src/GoatQuery/src/Token/Token.cs @@ -6,6 +6,8 @@ public enum TokenType STRING, INT, DECIMAL, + FLOAT, + DOUBLE, GUID, DATETIME, LPAREN, diff --git a/src/GoatQuery/tests/Filter/FilterLexerTest.cs b/src/GoatQuery/tests/Filter/FilterLexerTest.cs index 17ed0dd..03a9908 100644 --- a/src/GoatQuery/tests/Filter/FilterLexerTest.cs +++ b/src/GoatQuery/tests/Filter/FilterLexerTest.cs @@ -183,23 +183,100 @@ public static IEnumerable Parameters() yield return new object[] { - "id eq 10.50", + "id eq 10m", new KeyValuePair[] { new (TokenType.IDENT, "id"), new (TokenType.IDENT, "eq"), - new (TokenType.DECIMAL, "10.50"), + new (TokenType.DECIMAL, "10m"), } }; yield return new object[] { - "id ne 0.1121563052701180", + "id eq 10.50m", + new KeyValuePair[] + { + new (TokenType.IDENT, "id"), + new (TokenType.IDENT, "eq"), + new (TokenType.DECIMAL, "10.50m"), + } + }; + + yield return new object[] + { + "id eq 10.50M", + new KeyValuePair[] + { + new (TokenType.IDENT, "id"), + new (TokenType.IDENT, "eq"), + new (TokenType.DECIMAL, "10.50M"), + } + }; + + yield return new object[] + { + "id eq 10f", + new KeyValuePair[] + { + new (TokenType.IDENT, "id"), + new (TokenType.IDENT, "eq"), + new (TokenType.FLOAT, "10f"), + } + }; + + yield return new object[] + { + "id ne 0.1121563052701180f", new KeyValuePair[] { new (TokenType.IDENT, "id"), new (TokenType.IDENT, "ne"), - new (TokenType.DECIMAL, "0.1121563052701180"), + new (TokenType.FLOAT, "0.1121563052701180f"), + } + }; + + yield return new object[] + { + "id ne 0.1121563052701180F", + new KeyValuePair[] + { + new (TokenType.IDENT, "id"), + new (TokenType.IDENT, "ne"), + new (TokenType.FLOAT, "0.1121563052701180F"), + } + }; + + yield return new object[] + { + "id eq 10d", + new KeyValuePair[] + { + new (TokenType.IDENT, "id"), + new (TokenType.IDENT, "eq"), + new (TokenType.DOUBLE, "10d"), + } + }; + + yield return new object[] + { + "id eq 3.14159265359d", + new KeyValuePair[] + { + new (TokenType.IDENT, "id"), + new (TokenType.IDENT, "eq"), + new (TokenType.DOUBLE, "3.14159265359d"), + } + }; + + yield return new object[] + { + "id eq 3.14159265359D", + new KeyValuePair[] + { + new (TokenType.IDENT, "id"), + new (TokenType.IDENT, "eq"), + new (TokenType.DOUBLE, "3.14159265359D"), } }; diff --git a/src/GoatQuery/tests/Filter/FilterParserTest.cs b/src/GoatQuery/tests/Filter/FilterParserTest.cs index ceb3cdb..df8015a 100644 --- a/src/GoatQuery/tests/Filter/FilterParserTest.cs +++ b/src/GoatQuery/tests/Filter/FilterParserTest.cs @@ -9,7 +9,9 @@ public sealed class FilterParserTest [InlineData("Age ne 10", "Age", "ne", "10")] [InlineData("Name contains 'John'", "Name", "contains", "John")] [InlineData("Id eq e4c7772b-8947-4e46-98ed-644b417d2a08", "Id", "eq", "e4c7772b-8947-4e46-98ed-644b417d2a08")] - [InlineData("Id eq 0.1121563052701180", "Id", "eq", "0.1121563052701180")] + [InlineData("Id eq 3.14159265359f", "Id", "eq", "3.14159265359f")] + [InlineData("Id eq 3.14159265359m", "Id", "eq", "3.14159265359m")] + [InlineData("Id eq 3.14159265359d", "Id", "eq", "3.14159265359d")] [InlineData("Age lt 99", "Age", "lt", "99")] [InlineData("Age lte 99", "Age", "lte", "99")] [InlineData("Age gt 99", "Age", "gt", "99")] diff --git a/src/GoatQuery/tests/Filter/FilterTest.cs b/src/GoatQuery/tests/Filter/FilterTest.cs index 370ce76..8e48b84 100644 --- a/src/GoatQuery/tests/Filter/FilterTest.cs +++ b/src/GoatQuery/tests/Filter/FilterTest.cs @@ -4,12 +4,12 @@ public sealed class FilterTest { private static readonly Dictionary _users = new Dictionary { - ["John"] = new User { Age = 2, Firstname = "John", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), DateOfBirth = DateTime.Parse("2004-01-31 23:59:59"), Balance = 1.50m }, - ["Jane"] = new User { Age = 1, Firstname = "Jane", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), DateOfBirth = DateTime.Parse("2020-05-09 15:30:00"), Balance = 0 }, - ["Apple"] = new User { Age = 2, Firstname = "Apple", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), DateOfBirth = DateTime.Parse("1980-12-31 00:00:01"), Balance = 1204050.98m }, - ["Harry"] = new User { Age = 1, Firstname = "Harry", UserId = Guid.Parse("e4c7772b-8947-4e46-98ed-644b417d2a08"), DateOfBirth = DateTime.Parse("2002-08-01"), Balance = 0.5372958205929493m }, - ["Doe"] = new User { Age = 3, Firstname = "Doe", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), DateOfBirth = DateTime.Parse("2023-07-26 12:00:30"), Balance = null }, - ["Egg"] = new User { Age = 3, Firstname = "Egg", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), DateOfBirth = DateTime.Parse("2000-01-01 00:00:00"), Balance = 1334534453453433.33435443343231235652m }, + ["John"] = new User { Age = 2, Firstname = "John", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), DateOfBirth = DateTime.Parse("2004-01-31 23:59:59"), BalanceDecimal = 1.50m }, + ["Jane"] = new User { Age = 1, Firstname = "Jane", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), DateOfBirth = DateTime.Parse("2020-05-09 15:30:00"), BalanceDecimal = 0 }, + ["Apple"] = new User { Age = 2, Firstname = "Apple", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), DateOfBirth = DateTime.Parse("1980-12-31 00:00:01"), BalanceFloat = 1204050.98f }, + ["Harry"] = new User { Age = 1, Firstname = "Harry", UserId = Guid.Parse("e4c7772b-8947-4e46-98ed-644b417d2a08"), DateOfBirth = DateTime.Parse("2002-08-01"), BalanceDecimal = 0.5372958205929493m }, + ["Doe"] = new User { Age = 3, Firstname = "Doe", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), DateOfBirth = DateTime.Parse("2023-07-26 12:00:30"), BalanceDecimal = null }, + ["Egg"] = new User { Age = 3, Firstname = "Egg", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), DateOfBirth = DateTime.Parse("2000-01-01 00:00:00"), BalanceDouble = 1334534453453433.33435443343231235652d }, }; public static IEnumerable Parameters() @@ -104,11 +104,6 @@ public static IEnumerable Parameters() new[] { _users["Harry"] } }; - yield return new object[] { - "balance eq 0.5372958205929493", - new[] { _users["Harry"] } - }; - yield return new object[] { "age lt 3", new[] { _users["John"], _users["Jane"], _users["Apple"], _users["Harry"] } @@ -139,6 +134,41 @@ public static IEnumerable Parameters() new[] { _users["John"], _users["Apple"] } }; + yield return new object[] { + "balanceDecimal eq 1.50m", + new[] { _users["John"] } + }; + + yield return new object[] { + "balanceDecimal gt 1m", + new[] { _users["John"] } + }; + + yield return new object[] { + "balanceDecimal gt 0.50m", + new[] { _users["John"], _users["Harry"] } + }; + + yield return new object[] { + "balanceDecimal eq 0.5372958205929493m", + new[] { _users["Harry"] } + }; + + yield return new object[] { + "balanceDouble eq 1334534453453433.33435443343231235652d", + new[] { _users["Egg"] } + }; + + yield return new object[] { + "balanceFloat eq 1204050.98f", + new[] { _users["Apple"] } + }; + + yield return new object[] { + "balanceFloat gt 2204050f", + Array.Empty() + }; + yield return new object[] { "dateOfBirth eq 2000-01-01", new[] { _users["Egg"] } diff --git a/src/GoatQuery/tests/User.cs b/src/GoatQuery/tests/User.cs index 8eb4cf5..f93bd5d 100644 --- a/src/GoatQuery/tests/User.cs +++ b/src/GoatQuery/tests/User.cs @@ -5,7 +5,9 @@ public record User public int Age { get; set; } public Guid UserId { get; set; } public string Firstname { get; set; } = string.Empty; - public decimal? Balance { get; set; } + public decimal? BalanceDecimal { get; set; } + public double? BalanceDouble { get; set; } + public float? BalanceFloat { get; set; } public DateTime DateOfBirth { get; set; } }