Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Jamess-Lucass committed Jul 25, 2024
1 parent 0bc8c68 commit ef4ad8a
Show file tree
Hide file tree
Showing 12 changed files with 147 additions and 13 deletions.
1 change: 1 addition & 0 deletions example/Dto/UserDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
1 change: 1 addition & 0 deletions example/Entities/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
3 changes: 2 additions & 1 deletion example/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
10 changes: 10 additions & 0 deletions src/GoatQuery/src/Ast/StringLiteral.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
85 changes: 82 additions & 3 deletions src/GoatQuery/src/Evaluator/FilterEvaluator.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using FluentResults;
Expand All @@ -18,21 +19,36 @@ public static Result<Expression> Evaluate(QueryExpression expression, ParameterE

var property = Expression.Property(parameterExpression, propertyName);

ConstantExpression value = null;
ConstantExpression value;

switch (exp.Right)
{
case GuidLiteral literal:
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)
Expand All @@ -47,6 +63,8 @@ public static Result<Expression> 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}");
}
}

Expand Down Expand Up @@ -75,4 +93,65 @@ public static Result<Expression> Evaluate(QueryExpression expression, ParameterE

return null;
}

private static Result<ConstantExpression> 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

Check failure on line 105 in src/GoatQuery/src/Evaluator/FilterEvaluator.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Feature 'recursive patterns' is not available in C# 7.3. Please use language version 8.0 or greater.

Check failure on line 105 in src/GoatQuery/src/Evaluator/FilterEvaluator.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Feature 'recursive patterns' is not available in C# 7.3. Please use language version 8.0 or greater.
{
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}")

Check failure on line 115 in src/GoatQuery/src/Evaluator/FilterEvaluator.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Feature 'recursive patterns' is not available in C# 7.3. Please use language version 8.0 or greater.

Check failure on line 115 in src/GoatQuery/src/Evaluator/FilterEvaluator.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Feature 'recursive patterns' is not available in C# 7.3. Please use language version 8.0 or greater.
};

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<ConstantExpression> 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

Check failure on line 138 in src/GoatQuery/src/Evaluator/FilterEvaluator.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Feature 'recursive patterns' is not available in C# 7.3. Please use language version 8.0 or greater.

Check failure on line 138 in src/GoatQuery/src/Evaluator/FilterEvaluator.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Feature 'recursive patterns' is not available in C# 7.3. Please use language version 8.0 or greater.
{
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}")

Check failure on line 143 in src/GoatQuery/src/Evaluator/FilterEvaluator.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Feature 'recursive patterns' is not available in C# 7.3. Please use language version 8.0 or greater.

Check failure on line 143 in src/GoatQuery/src/Evaluator/FilterEvaluator.cs

View workflow job for this annotation

GitHub Actions / build (8.0.x)

Feature 'recursive patterns' is not available in C# 7.3. Please use language version 8.0 or greater.
};

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}");
}
}
}
10 changes: 8 additions & 2 deletions src/GoatQuery/src/Lexer/Lexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -87,7 +93,7 @@ private string ReadIdentifier()
{
var currentPosition = _position;

while (IsLetter(_character) || IsDigit(_character))
while (IsLetter(_character) || IsDigit(_character) || _character == '-' || _character == '.')
{
ReadCharacter();
}
Expand All @@ -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)
Expand Down
8 changes: 7 additions & 1 deletion src/GoatQuery/src/Parser/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ private Result<InfixExpression> 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");
}
Expand Down Expand Up @@ -168,6 +168,12 @@ private Result<InfixExpression> 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;
Expand Down
1 change: 1 addition & 0 deletions src/GoatQuery/src/Token/Token.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public enum TokenType
IDENT,
STRING,
INT,
DECIMAL,
GUID,
LPAREN,
RPAREN,
Expand Down
22 changes: 22 additions & 0 deletions src/GoatQuery/tests/Filter/FilterLexerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,28 @@ public static IEnumerable<object[]> Parameters()
new (TokenType.GUID, "e4c7772b-8947-4e46-98ed-644b417d2a08"),
}
};

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

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

[Theory]
Expand Down
1 change: 1 addition & 0 deletions src/GoatQuery/tests/Filter/FilterParserTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
17 changes: 11 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") },
["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<object[]> Parameters()
Expand Down Expand Up @@ -103,6 +103,11 @@ public static IEnumerable<object[]> Parameters()
"UserId eq e4c7772b-8947-4e46-98ed-644b417d2a08",
new[] { _users["Harry"] }
};

yield return new object[] {
"balance eq 0.5372958205929493",
new[] { _users["Harry"] }
};
}

[Theory]
Expand Down
1 change: 1 addition & 0 deletions src/GoatQuery/tests/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit ef4ad8a

Please sign in to comment.