Skip to content

Commit

Permalink
[v2] Added decimal, float & double support for filter (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jamess-Lucass authored Jul 31, 2024
1 parent fc0160f commit 444f8c0
Show file tree
Hide file tree
Showing 12 changed files with 235 additions and 8 deletions.
1 change: 1 addition & 0 deletions example/Dto/UserDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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; }
public DateTime DateOfBirthUtc { get; set; }
public DateTime DateOfBirthTz { get; set; }
}
1 change: 1 addition & 0 deletions example/Entities/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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; }

[Column(TypeName = "timestamp with time zone")]
public DateTime DateOfBirthUtc { get; set; }
Expand Down
1 change: 1 addition & 0 deletions example/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
.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.Test, f => f.Random.Double())
.Rules((f, u) =>
{
var timeZone = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
Expand Down
30 changes: 30 additions & 0 deletions src/GoatQuery/src/Ast/StringLiteral.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,36 @@ public IntegerLiteral(Token token, int value) : base(token)
}
}

public sealed class DecimalLiteral : QueryExpression
{
public decimal Value { get; set; }

public DecimalLiteral(Token token, decimal value) : base(token)
{
Value = value;
}
}

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; }
Expand Down
9 changes: 9 additions & 0 deletions src/GoatQuery/src/Evaluator/FilterEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ public static Result<Expression> Evaluate(QueryExpression expression, ParameterE

value = integerConstant.Value;
break;
case DecimalLiteral literal:
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);
break;
Expand Down
18 changes: 18 additions & 0 deletions src/GoatQuery/src/Lexer/Lexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,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;
}
Expand Down
28 changes: 26 additions & 2 deletions src/GoatQuery/src/Parser/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ private Result<InfixExpression> ParseFilterStatement()

var statement = new InfixExpression(_currentToken, identifier, _currentToken.Literal);

if (!PeekTokenIn(TokenType.STRING, TokenType.INT, TokenType.GUID, TokenType.DATETIME))
if (!PeekTokenIn(TokenType.STRING, TokenType.INT, TokenType.GUID, TokenType.DATETIME, TokenType.DECIMAL, TokenType.FLOAT, TokenType.DOUBLE))
{
return Result.Fail("Invalid value type within filter");
}
Expand All @@ -152,7 +152,7 @@ private Result<InfixExpression> 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");
}
Expand All @@ -174,6 +174,30 @@ private Result<InfixExpression> 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:
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))
{
Expand Down
3 changes: 3 additions & 0 deletions src/GoatQuery/src/Token/Token.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ public enum TokenType
IDENT,
STRING,
INT,
DECIMAL,
FLOAT,
DOUBLE,
GUID,
DATETIME,
LPAREN,
Expand Down
99 changes: 99 additions & 0 deletions src/GoatQuery/tests/Filter/FilterLexerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,105 @@ public static IEnumerable<object[]> Parameters()
}
};

yield return new object[]
{
"id eq 10m",
new KeyValuePair<TokenType, string>[]
{
new (TokenType.IDENT, "id"),
new (TokenType.IDENT, "eq"),
new (TokenType.DECIMAL, "10m"),
}
};

yield return new object[]
{
"id eq 10.50m",
new KeyValuePair<TokenType, string>[]
{
new (TokenType.IDENT, "id"),
new (TokenType.IDENT, "eq"),
new (TokenType.DECIMAL, "10.50m"),
}
};

yield return new object[]
{
"id eq 10.50M",
new KeyValuePair<TokenType, string>[]
{
new (TokenType.IDENT, "id"),
new (TokenType.IDENT, "eq"),
new (TokenType.DECIMAL, "10.50M"),
}
};

yield return new object[]
{
"id eq 10f",
new KeyValuePair<TokenType, string>[]
{
new (TokenType.IDENT, "id"),
new (TokenType.IDENT, "eq"),
new (TokenType.FLOAT, "10f"),
}
};

yield return new object[]
{
"id ne 0.1121563052701180f",
new KeyValuePair<TokenType, string>[]
{
new (TokenType.IDENT, "id"),
new (TokenType.IDENT, "ne"),
new (TokenType.FLOAT, "0.1121563052701180f"),
}
};

yield return new object[]
{
"id ne 0.1121563052701180F",
new KeyValuePair<TokenType, string>[]
{
new (TokenType.IDENT, "id"),
new (TokenType.IDENT, "ne"),
new (TokenType.FLOAT, "0.1121563052701180F"),
}
};

yield return new object[]
{
"id eq 10d",
new KeyValuePair<TokenType, string>[]
{
new (TokenType.IDENT, "id"),
new (TokenType.IDENT, "eq"),
new (TokenType.DOUBLE, "10d"),
}
};

yield return new object[]
{
"id eq 3.14159265359d",
new KeyValuePair<TokenType, string>[]
{
new (TokenType.IDENT, "id"),
new (TokenType.IDENT, "eq"),
new (TokenType.DOUBLE, "3.14159265359d"),
}
};

yield return new object[]
{
"id eq 3.14159265359D",
new KeyValuePair<TokenType, string>[]
{
new (TokenType.IDENT, "id"),
new (TokenType.IDENT, "eq"),
new (TokenType.DOUBLE, "3.14159265359D"),
}
};

yield return new object[]
{
"age lt 50",
Expand Down
3 changes: 3 additions & 0 deletions src/GoatQuery/tests/Filter/FilterParserTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +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 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")]
Expand Down
47 changes: 41 additions & 6 deletions src/GoatQuery/tests/Filter/FilterTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ public sealed class FilterTest
{
private static readonly Dictionary<string, User> _users = new Dictionary<string, User>
{
["John"] = new User { Age = 2, Firstname = "John", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), DateOfBirth = DateTime.Parse("2004-01-31 23:59:59") },
["Jane"] = new User { Age = 1, Firstname = "Jane", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), DateOfBirth = DateTime.Parse("2020-05-09 15:30:00") },
["Apple"] = new User { Age = 2, Firstname = "Apple", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), DateOfBirth = DateTime.Parse("1980-12-31 00:00:01") },
["Harry"] = new User { Age = 1, Firstname = "Harry", UserId = Guid.Parse("e4c7772b-8947-4e46-98ed-644b417d2a08"), DateOfBirth = DateTime.Parse("2002-08-01") },
["Doe"] = new User { Age = 3, Firstname = "Doe", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), DateOfBirth = DateTime.Parse("2023-07-26 12:00:30") },
["Egg"] = new User { Age = 3, Firstname = "Egg", UserId = Guid.Parse("58cdeca3-645b-457c-87aa-7d5f87734255"), DateOfBirth = DateTime.Parse("2000-01-01 00:00:00") },
["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<object[]> Parameters()
Expand Down Expand Up @@ -134,6 +134,41 @@ public static IEnumerable<object[]> 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<User>()
};

yield return new object[] {
"dateOfBirth eq 2000-01-01",
new[] { _users["Egg"] }
Expand Down
3 changes: 3 additions & 0 deletions src/GoatQuery/tests/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ public record User
public int Age { get; set; }
public Guid UserId { get; set; }
public string Firstname { get; set; } = string.Empty;
public decimal? BalanceDecimal { get; set; }
public double? BalanceDouble { get; set; }
public float? BalanceFloat { get; set; }
public DateTime DateOfBirth { get; set; }
}

Expand Down

0 comments on commit 444f8c0

Please sign in to comment.