Skip to content

Commit 444f8c0

Browse files
[v2] Added decimal, float & double support for filter (#71)
1 parent fc0160f commit 444f8c0

File tree

12 files changed

+235
-8
lines changed

12 files changed

+235
-8
lines changed

example/Dto/UserDto.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public record UserDto
88
public string Firstname { get; set; } = string.Empty;
99
public string Lastname { get; set; } = string.Empty;
1010
public int Age { get; set; }
11+
public double Test { get; set; }
1112
public DateTime DateOfBirthUtc { get; set; }
1213
public DateTime DateOfBirthTz { get; set; }
1314
}

example/Entities/User.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public record User
77
public string Lastname { get; set; } = string.Empty;
88
public int Age { get; set; }
99
public bool IsDeleted { get; set; }
10+
public double Test { get; set; }
1011

1112
[Column(TypeName = "timestamp with time zone")]
1213
public DateTime DateOfBirthUtc { get; set; }

example/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
.RuleFor(x => x.Lastname, f => f.Person.LastName)
4141
.RuleFor(x => x.Age, f => f.Random.Int(0, 100))
4242
.RuleFor(x => x.IsDeleted, f => f.Random.Bool())
43+
.RuleFor(x => x.Test, f => f.Random.Double())
4344
.Rules((f, u) =>
4445
{
4546
var timeZone = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");

src/GoatQuery/src/Ast/StringLiteral.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,36 @@ public IntegerLiteral(Token token, int value) : base(token)
3030
}
3131
}
3232

33+
public sealed class DecimalLiteral : QueryExpression
34+
{
35+
public decimal Value { get; set; }
36+
37+
public DecimalLiteral(Token token, decimal value) : base(token)
38+
{
39+
Value = value;
40+
}
41+
}
42+
43+
public sealed class FloatLiteral : QueryExpression
44+
{
45+
public float Value { get; set; }
46+
47+
public FloatLiteral(Token token, float value) : base(token)
48+
{
49+
Value = value;
50+
}
51+
}
52+
53+
public sealed class DoubleLiteral : QueryExpression
54+
{
55+
public double Value { get; set; }
56+
57+
public DoubleLiteral(Token token, double value) : base(token)
58+
{
59+
Value = value;
60+
}
61+
}
62+
3363
public sealed class DateTimeLiteral : QueryExpression
3464
{
3565
public DateTime Value { get; set; }

src/GoatQuery/src/Evaluator/FilterEvaluator.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,15 @@ public static Result<Expression> Evaluate(QueryExpression expression, ParameterE
3636

3737
value = integerConstant.Value;
3838
break;
39+
case DecimalLiteral literal:
40+
value = Expression.Constant(literal.Value, property.Type);
41+
break;
42+
case FloatLiteral literal:
43+
value = Expression.Constant(literal.Value, property.Type);
44+
break;
45+
case DoubleLiteral literal:
46+
value = Expression.Constant(literal.Value, property.Type);
47+
break;
3948
case StringLiteral literal:
4049
value = Expression.Constant(literal.Value, property.Type);
4150
break;

src/GoatQuery/src/Lexer/Lexer.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,24 @@ public Token NextToken()
7070
return token;
7171
}
7272

73+
if (token.Literal.EndsWith("f", StringComparison.OrdinalIgnoreCase))
74+
{
75+
token.Type = TokenType.FLOAT;
76+
return token;
77+
}
78+
79+
if (token.Literal.EndsWith("m", StringComparison.OrdinalIgnoreCase))
80+
{
81+
token.Type = TokenType.DECIMAL;
82+
return token;
83+
}
84+
85+
if (token.Literal.EndsWith("d", StringComparison.OrdinalIgnoreCase))
86+
{
87+
token.Type = TokenType.DOUBLE;
88+
return token;
89+
}
90+
7391
token.Type = TokenType.INT;
7492
return token;
7593
}

src/GoatQuery/src/Parser/Parser.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ private Result<InfixExpression> ParseFilterStatement()
140140

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

143-
if (!PeekTokenIn(TokenType.STRING, TokenType.INT, TokenType.GUID, TokenType.DATETIME))
143+
if (!PeekTokenIn(TokenType.STRING, TokenType.INT, TokenType.GUID, TokenType.DATETIME, TokenType.DECIMAL, TokenType.FLOAT, TokenType.DOUBLE))
144144
{
145145
return Result.Fail("Invalid value type within filter");
146146
}
@@ -152,7 +152,7 @@ private Result<InfixExpression> ParseFilterStatement()
152152
return Result.Fail("Value must be a string when using 'contains' operand");
153153
}
154154

155-
if (statement.Operator.In(Keywords.Lt, Keywords.Lte, Keywords.Gt, Keywords.Gte) && !CurrentTokenIn(TokenType.INT, TokenType.DATETIME))
155+
if (statement.Operator.In(Keywords.Lt, Keywords.Lte, Keywords.Gt, Keywords.Gte) && !CurrentTokenIn(TokenType.INT, TokenType.DECIMAL, TokenType.FLOAT, TokenType.DOUBLE, TokenType.DATETIME))
156156
{
157157
return Result.Fail($"Value must be an integer when using '{statement.Operator}' operand");
158158
}
@@ -174,6 +174,30 @@ private Result<InfixExpression> ParseFilterStatement()
174174
statement.Right = new IntegerLiteral(_currentToken, intValue);
175175
}
176176
break;
177+
case TokenType.FLOAT:
178+
var floatValueWithoutSuffixLiteral = _currentToken.Literal.TrimEnd('f');
179+
180+
if (float.TryParse(floatValueWithoutSuffixLiteral, out var floatValue))
181+
{
182+
statement.Right = new FloatLiteral(_currentToken, floatValue);
183+
}
184+
break;
185+
case TokenType.DECIMAL:
186+
var decimalValueWithoutSuffixLiteral = _currentToken.Literal.TrimEnd('m');
187+
188+
if (decimal.TryParse(decimalValueWithoutSuffixLiteral, out var decimalValue))
189+
{
190+
statement.Right = new DecimalLiteral(_currentToken, decimalValue);
191+
}
192+
break;
193+
case TokenType.DOUBLE:
194+
var doubleValueWithoutSuffixLiteral = _currentToken.Literal.TrimEnd('d');
195+
196+
if (double.TryParse(doubleValueWithoutSuffixLiteral, out var doubleValue))
197+
{
198+
statement.Right = new DoubleLiteral(_currentToken, doubleValue);
199+
}
200+
break;
177201
case TokenType.DATETIME:
178202
if (DateTime.TryParse(_currentToken.Literal, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, out var dateTimeValue))
179203
{

src/GoatQuery/src/Token/Token.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ public enum TokenType
55
IDENT,
66
STRING,
77
INT,
8+
DECIMAL,
9+
FLOAT,
10+
DOUBLE,
811
GUID,
912
DATETIME,
1013
LPAREN,

src/GoatQuery/tests/Filter/FilterLexerTest.cs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,105 @@ public static IEnumerable<object[]> Parameters()
181181
}
182182
};
183183

184+
yield return new object[]
185+
{
186+
"id eq 10m",
187+
new KeyValuePair<TokenType, string>[]
188+
{
189+
new (TokenType.IDENT, "id"),
190+
new (TokenType.IDENT, "eq"),
191+
new (TokenType.DECIMAL, "10m"),
192+
}
193+
};
194+
195+
yield return new object[]
196+
{
197+
"id eq 10.50m",
198+
new KeyValuePair<TokenType, string>[]
199+
{
200+
new (TokenType.IDENT, "id"),
201+
new (TokenType.IDENT, "eq"),
202+
new (TokenType.DECIMAL, "10.50m"),
203+
}
204+
};
205+
206+
yield return new object[]
207+
{
208+
"id eq 10.50M",
209+
new KeyValuePair<TokenType, string>[]
210+
{
211+
new (TokenType.IDENT, "id"),
212+
new (TokenType.IDENT, "eq"),
213+
new (TokenType.DECIMAL, "10.50M"),
214+
}
215+
};
216+
217+
yield return new object[]
218+
{
219+
"id eq 10f",
220+
new KeyValuePair<TokenType, string>[]
221+
{
222+
new (TokenType.IDENT, "id"),
223+
new (TokenType.IDENT, "eq"),
224+
new (TokenType.FLOAT, "10f"),
225+
}
226+
};
227+
228+
yield return new object[]
229+
{
230+
"id ne 0.1121563052701180f",
231+
new KeyValuePair<TokenType, string>[]
232+
{
233+
new (TokenType.IDENT, "id"),
234+
new (TokenType.IDENT, "ne"),
235+
new (TokenType.FLOAT, "0.1121563052701180f"),
236+
}
237+
};
238+
239+
yield return new object[]
240+
{
241+
"id ne 0.1121563052701180F",
242+
new KeyValuePair<TokenType, string>[]
243+
{
244+
new (TokenType.IDENT, "id"),
245+
new (TokenType.IDENT, "ne"),
246+
new (TokenType.FLOAT, "0.1121563052701180F"),
247+
}
248+
};
249+
250+
yield return new object[]
251+
{
252+
"id eq 10d",
253+
new KeyValuePair<TokenType, string>[]
254+
{
255+
new (TokenType.IDENT, "id"),
256+
new (TokenType.IDENT, "eq"),
257+
new (TokenType.DOUBLE, "10d"),
258+
}
259+
};
260+
261+
yield return new object[]
262+
{
263+
"id eq 3.14159265359d",
264+
new KeyValuePair<TokenType, string>[]
265+
{
266+
new (TokenType.IDENT, "id"),
267+
new (TokenType.IDENT, "eq"),
268+
new (TokenType.DOUBLE, "3.14159265359d"),
269+
}
270+
};
271+
272+
yield return new object[]
273+
{
274+
"id eq 3.14159265359D",
275+
new KeyValuePair<TokenType, string>[]
276+
{
277+
new (TokenType.IDENT, "id"),
278+
new (TokenType.IDENT, "eq"),
279+
new (TokenType.DOUBLE, "3.14159265359D"),
280+
}
281+
};
282+
184283
yield return new object[]
185284
{
186285
"age lt 50",

src/GoatQuery/tests/Filter/FilterParserTest.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ public sealed class FilterParserTest
99
[InlineData("Age ne 10", "Age", "ne", "10")]
1010
[InlineData("Name contains 'John'", "Name", "contains", "John")]
1111
[InlineData("Id eq e4c7772b-8947-4e46-98ed-644b417d2a08", "Id", "eq", "e4c7772b-8947-4e46-98ed-644b417d2a08")]
12+
[InlineData("Id eq 3.14159265359f", "Id", "eq", "3.14159265359f")]
13+
[InlineData("Id eq 3.14159265359m", "Id", "eq", "3.14159265359m")]
14+
[InlineData("Id eq 3.14159265359d", "Id", "eq", "3.14159265359d")]
1215
[InlineData("Age lt 99", "Age", "lt", "99")]
1316
[InlineData("Age lte 99", "Age", "lte", "99")]
1417
[InlineData("Age gt 99", "Age", "gt", "99")]

0 commit comments

Comments
 (0)