Skip to content

Commit ddf44a1

Browse files
committed
feat: better count and number filtering
fixes #45
1 parent 4ac9d90 commit ddf44a1

File tree

10 files changed

+84
-12
lines changed

10 files changed

+84
-12
lines changed

QueryKit.IntegrationTests/Tests/DatabaseFilteringTests.cs

+29
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ namespace QueryKit.IntegrationTests.Tests;
1111
using SharedTestingHelper.Fakes.Recipes;
1212
using WebApiTestProject.Database;
1313
using WebApiTestProject.Entities;
14+
using WebApiTestProject.Entities.Ingredients;
15+
using WebApiTestProject.Entities.Ingredients.Models;
1416
using WebApiTestProject.Entities.Recipes;
1517
using Xunit.Abstractions;
1618

@@ -337,6 +339,33 @@ public async Task can_filter_by_string_for_collection_contains()
337339
recipes[0].Id.Should().Be(fakeRecipeOne.Id);
338340
}
339341

342+
[Fact]
343+
public async Task can_filter_within_collection_long()
344+
{
345+
var testingServiceScope = new TestingServiceScope();
346+
var qualityLevel = 2L;
347+
var fakeRecipeOne = new FakeRecipeBuilder().Build();
348+
var ingredient = new FakeIngredientBuilder()
349+
.WithQualityLevel(qualityLevel)
350+
.Build();
351+
fakeRecipeOne.AddIngredient(ingredient);
352+
353+
await testingServiceScope.InsertAsync(fakeRecipeOne);
354+
355+
var input = $"Ingredients.QualityLevel == {qualityLevel}";
356+
var config = new QueryKitConfiguration(settings =>
357+
{
358+
settings.Property<Recipe>(x => x.Ingredients.Select(y => y.QualityLevel)).PreventSort();
359+
});
360+
361+
var queryableRecipes = testingServiceScope.DbContext().Recipes;
362+
var appliedQueryable = queryableRecipes.ApplyQueryKitFilter(input);
363+
var recipes = await appliedQueryable.ToListAsync();
364+
365+
recipes.Count.Should().Be(1);
366+
recipes[0].Id.Should().Be(fakeRecipeOne.Id);
367+
}
368+
340369
[Fact(Skip = "Can not handle nested collections yet.")]
341370
public async Task can_filter_by_string_for_nested_collection()
342371
{

QueryKit.WebApiTestProject/Entities/Ingredients/Ingredient.cs

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class Ingredient : BaseEntity
1616
public string Name { get; private set; }
1717

1818
public string Quantity { get; private set; }
19+
public long? QualityLevel { get; set; }
1920

2021
public DateTime? ExpiresOn { get; private set; }
2122

@@ -39,6 +40,7 @@ public static Ingredient Create(IngredientForCreation ingredientForCreation)
3940
newIngredient.ExpiresOn = ingredientForCreation.ExpiresOn;
4041
newIngredient.Measure = ingredientForCreation.Measure;
4142
newIngredient.RecipeId = ingredientForCreation.RecipeId;
43+
newIngredient.QualityLevel = ingredientForCreation.QualityLevel;
4244
return newIngredient;
4345
}
4446

@@ -49,6 +51,7 @@ public Ingredient Update(IngredientForUpdate ingredientForUpdate)
4951
ExpiresOn = ingredientForUpdate.ExpiresOn;
5052
Measure = ingredientForUpdate.Measure;
5153
RecipeId = ingredientForUpdate.RecipeId;
54+
QualityLevel = ingredientForUpdate.QualityLevel;
5255
return this;
5356
}
5457

QueryKit.WebApiTestProject/Entities/Ingredients/Models/IngredientForCreation.cs

+1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ public sealed class IngredientForCreation
66
public string Quantity { get; set; }
77
public DateTime? ExpiresOn { get; set; }
88
public string Measure { get; set; }
9+
public long? QualityLevel { get; set; }
910
public Guid RecipeId { get; set; }
1011
}

QueryKit.WebApiTestProject/Entities/Ingredients/Models/IngredientForUpdate.cs

+1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ public sealed class IngredientForUpdate
66
public string Quantity { get; set; }
77
public DateTime? ExpiresOn { get; set; }
88
public string Measure { get; set; }
9+
public long? QualityLevel { get; set; }
910
public Guid RecipeId { get; set; }
1011
}

QueryKit.WebApiTestProject/Migrations/20240501011638_BaseTestingMigration.Designer.cs QueryKit.WebApiTestProject/Migrations/20240622235558_TestingSetup.Designer.cs

+6-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

QueryKit.WebApiTestProject/Migrations/20240501011638_BaseTestingMigration.cs QueryKit.WebApiTestProject/Migrations/20240622235558_TestingSetup.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
namespace QueryKit.WebApiTestProject.Migrations
88
{
99
/// <inheritdoc />
10-
public partial class BaseTestingMigration : Migration
10+
public partial class TestingSetup : Migration
1111
{
1212
/// <inheritdoc />
1313
protected override void Up(MigrationBuilder migrationBuilder)
@@ -90,6 +90,7 @@ protected override void Up(MigrationBuilder migrationBuilder)
9090
id = table.Column<Guid>(type: "uuid", nullable: false),
9191
name = table.Column<string>(type: "text", nullable: false),
9292
quantity = table.Column<string>(type: "text", nullable: false),
93+
quality_level = table.Column<long>(type: "bigint", nullable: true),
9394
expires_on = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
9495
measure = table.Column<string>(type: "text", nullable: false),
9596
minimum_quality = table.Column<int>(type: "integer", nullable: false),

QueryKit.WebApiTestProject/Migrations/TestingDbContextModelSnapshot.cs

+4
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ protected override void BuildModel(ModelBuilder modelBuilder)
7575
.HasColumnType("text")
7676
.HasColumnName("name");
7777

78+
b.Property<long?>("QualityLevel")
79+
.HasColumnType("bigint")
80+
.HasColumnName("quality_level");
81+
7882
b.Property<string>("Quantity")
7983
.IsRequired()
8084
.HasColumnType("text")

QueryKit/FilterParser.cs

+7-8
Original file line numberDiff line numberDiff line change
@@ -179,25 +179,24 @@ from closingBracket in Parse.Char(']')
179179
{ typeof(sbyte), value => sbyte.Parse(value, CultureInfo.InvariantCulture) },
180180
};
181181

182-
private static Expression CreateRightExpr(Expression leftExpr, string right)
182+
private static Expression CreateRightExpr(Expression leftExpr, string right, ComparisonOperator op)
183183
{
184184
var targetType = leftExpr.Type;
185-
return CreateRightExprFromType(targetType, right);
185+
return CreateRightExprFromType(targetType, right, op);
186186
}
187187

188-
private static Expression CreateRightExprFromType(Type leftExprType, string right)
188+
private static Expression CreateRightExprFromType(Type leftExprType, string right, ComparisonOperator op)
189189
{
190190
var isEnumerable = IsEnumerable(leftExprType);
191191
var targetType = leftExprType;
192192
if (isEnumerable)
193193
{
194-
if (int.TryParse(right, out var intVal) && targetType.GetGenericArguments().All(arg => arg != typeof(string)))
194+
if (op.IsCountOperator() && int.TryParse(right, out var intVal))
195195
{
196-
// supports collection count
197196
return Expression.Constant(intVal, typeof(int));
198197
}
199198
targetType = targetType.GetGenericArguments()[0];
200-
return CreateRightExprFromType(targetType, right);
199+
return CreateRightExprFromType(targetType, right, op);
201200
}
202201

203202
var rawType = targetType;
@@ -444,11 +443,11 @@ private static Parser<Expression> ComparisonExprParser<T>(ParameterExpression pa
444443
) :
445444
Expression.Call(temp.leftExpr, toStringMethod!);
446445

447-
return temp.op.GetExpression<T>(leftExpr, CreateRightExpr(temp.leftExpr, temp.right), config?.DbContextType);
446+
return temp.op.GetExpression<T>(leftExpr, CreateRightExpr(temp.leftExpr, temp.right, temp.op), config?.DbContextType);
448447
}
449448

450449

451-
var rightExpr = CreateRightExpr(temp.leftExpr, temp.right);
450+
var rightExpr = CreateRightExpr(temp.leftExpr, temp.right, temp.op);
452451
return temp.op.GetExpression<T>(temp.leftExpr, rightExpr, config?.DbContextType);
453452
});
454453
}

QueryKit/Operators/ComparisonOperator.cs

+25-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ public abstract class ComparisonOperator : SmartEnum<ComparisonOperator>
5858
public static ComparisonOperator HasCountLessThanOrEqualOperator(bool caseInsensitive = false, bool usesAll = false) => new HasCountLessThanOrEqualType(caseInsensitive);
5959
public static ComparisonOperator HasOperator(bool caseInsensitive = false, bool usesAll = false) => new HasType(caseInsensitive);
6060
public static ComparisonOperator DoesNotHaveOperator(bool caseInsensitive = false, bool usesAll = false) => new DoesNotHaveType(caseInsensitive);
61-
6261

6362
public static ComparisonOperator GetByOperatorString(string op, bool caseInsensitive = false, bool usesAll = false)
6463
{
@@ -175,6 +174,7 @@ public static ComparisonOperator GetByOperatorString(string op, bool caseInsensi
175174
public const char CaseSensitiveAppendix = '*';
176175
public const char AllPrefix = '%';
177176
public abstract string Operator();
177+
public abstract bool IsCountOperator();
178178
public bool CaseInsensitive { get; protected set; }
179179
public bool UsesAll { get; protected set; }
180180
public abstract Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType);
@@ -191,6 +191,7 @@ public EqualsType(bool caseInsensitive = false, bool usesAll = false) : base("==
191191
}
192192

193193
public override string Operator() => CaseInsensitive ? $"{Name}{CaseSensitiveAppendix}" : Name;
194+
public override bool IsCountOperator() => false;
194195
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
195196
{
196197
if (left.Type.IsGenericType && left.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
@@ -223,6 +224,7 @@ public NotEqualsType(bool caseInsensitive = false, bool usesAll = false) : base(
223224
}
224225

225226
public override string Operator() => CaseInsensitive ? $"{Name}{CaseSensitiveAppendix}" : Name;
227+
public override bool IsCountOperator() => false;
226228
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
227229
{
228230
if (left.Type.IsGenericType && left.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
@@ -255,6 +257,7 @@ public GreaterThanType(bool caseInsensitive = false, bool usesAll = false) : bas
255257
}
256258

257259
public override string Operator() => Name;
260+
public override bool IsCountOperator() => false;
258261
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
259262
{
260263
if (left.Type.IsGenericType && left.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
@@ -272,6 +275,7 @@ public LessThanType(bool caseInsensitive = false, bool usesAll = false) : base("
272275
}
273276

274277
public override string Operator() => Name;
278+
public override bool IsCountOperator() => false;
275279
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
276280
{
277281
if (left.Type.IsGenericType && left.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
@@ -285,6 +289,7 @@ public override Expression GetExpression<T>(Expression left, Expression right, T
285289
private class GreaterThanOrEqualType : ComparisonOperator
286290
{
287291
public override string Operator() => Name;
292+
public override bool IsCountOperator() => false;
288293
public GreaterThanOrEqualType(bool caseInsensitive = false, bool usesAll = false) : base(">=", 4, caseInsensitive, usesAll)
289294
{
290295
}
@@ -304,6 +309,7 @@ public LessThanOrEqualType(bool caseInsensitive = false, bool usesAll = false) :
304309
{
305310
}
306311
public override string Operator() => Name;
312+
public override bool IsCountOperator() => false;
307313
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
308314
{
309315
if (left.Type.IsGenericType && left.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
@@ -321,6 +327,7 @@ public ContainsType(bool caseInsensitive = false, bool usesAll = false) : base("
321327
}
322328

323329
public override string Operator() => CaseInsensitive ? $"{Name}{CaseSensitiveAppendix}" : Name;
330+
public override bool IsCountOperator() => false;
324331
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
325332
{
326333
if (left.Type.IsGenericType && left.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
@@ -348,6 +355,7 @@ public StartsWithType(bool caseInsensitive = false, bool usesAll = false) : base
348355
}
349356

350357
public override string Operator() => CaseInsensitive ? $"{Name}{CaseSensitiveAppendix}" : Name;
358+
public override bool IsCountOperator() => false;
351359
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
352360
{
353361
if (left.Type.IsGenericType && left.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
@@ -375,6 +383,7 @@ public EndsWithType(bool caseInsensitive = false, bool usesAll = false) : base("
375383
}
376384

377385
public override string Operator() => CaseInsensitive ? $"{Name}{CaseSensitiveAppendix}" : Name;
386+
public override bool IsCountOperator() => false;
378387
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
379388
{
380389
if (left.Type.IsGenericType && left.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
@@ -402,6 +411,7 @@ public NotContainsType(bool caseInsensitive = false, bool usesAll = false) : bas
402411
}
403412

404413
public override string Operator() => CaseInsensitive ? $"{Name}{CaseSensitiveAppendix}" : Name;
414+
public override bool IsCountOperator() => false;
405415
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
406416
{
407417
if (left.Type.IsGenericType && left.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
@@ -430,6 +440,7 @@ public NotStartsWithType(bool caseInsensitive = false, bool usesAll = false) : b
430440
}
431441

432442
public override string Operator() => CaseInsensitive ? $"{Name}{CaseSensitiveAppendix}" : Name;
443+
public override bool IsCountOperator() => false;
433444
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
434445
{
435446
if (left.Type.IsGenericType && left.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
@@ -457,6 +468,7 @@ public NotEndsWithType(bool caseInsensitive = false, bool usesAll = false) : bas
457468
}
458469

459470
public override string Operator() => CaseInsensitive ? $"{Name}{CaseSensitiveAppendix}" : Name;
471+
public override bool IsCountOperator() => false;
460472
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
461473
{
462474
if (left.Type.IsGenericType && left.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
@@ -484,6 +496,7 @@ public InType(bool caseInsensitive = false, bool usesAll = false) : base("^^", 1
484496
}
485497

486498
public override string Operator() => CaseInsensitive ? $"{Name}{CaseSensitiveAppendix}" : Name;
499+
public override bool IsCountOperator() => false;
487500
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
488501
{
489502
var leftType = left.Type == typeof(Guid) || left.Type == typeof(Guid?)
@@ -533,6 +546,7 @@ public SoundsLikeType(bool caseInsensitive = false, bool usesAll = false) : base
533546
}
534547

535548
public override string Operator() => Name;
549+
public override bool IsCountOperator() => false;
536550

537551
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
538552
{
@@ -561,6 +575,7 @@ public DoesNotSoundLikeType(bool caseInsensitive = false, bool usesAll = false)
561575
}
562576

563577
public override string Operator() => Name;
578+
public override bool IsCountOperator() => false;
564579

565580
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
566581
{
@@ -589,6 +604,7 @@ public HasCountEqualToType(bool caseInsensitive = false, bool usesAll = false) :
589604
}
590605

591606
public override string Operator() => CaseInsensitive ? $"{Name}{CaseSensitiveAppendix}" : Name;
607+
public override bool IsCountOperator() => true;
592608
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
593609
{
594610
return GetCountExpression(left, right, nameof(Expression.Equal));
@@ -602,6 +618,7 @@ public HasCountNotEqualToType(bool caseInsensitive = false, bool usesAll = false
602618
}
603619

604620
public override string Operator() => CaseInsensitive ? $"{Name}{CaseSensitiveAppendix}" : Name;
621+
public override bool IsCountOperator() => true;
605622
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
606623
{
607624
return GetCountExpression(left, right, nameof(Expression.NotEqual));
@@ -615,6 +632,7 @@ public HasCountGreaterThanType(bool caseInsensitive = false, bool usesAll = fals
615632
}
616633

617634
public override string Operator() => CaseInsensitive ? $"{Name}{CaseSensitiveAppendix}" : Name;
635+
public override bool IsCountOperator() => true;
618636
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
619637
{
620638
return GetCountExpression(left, right, nameof(Expression.GreaterThan));
@@ -628,6 +646,7 @@ public HasCountLessThanType(bool caseInsensitive = false, bool usesAll = false)
628646
}
629647

630648
public override string Operator() => CaseInsensitive ? $"{Name}{CaseSensitiveAppendix}" : Name;
649+
public override bool IsCountOperator() => true;
631650
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
632651
{
633652
return GetCountExpression(left, right, nameof(Expression.LessThan));
@@ -641,6 +660,7 @@ public HasCountGreaterThanOrEqualType(bool caseInsensitive = false, bool usesAll
641660
}
642661

643662
public override string Operator() => CaseInsensitive ? $"{Name}{CaseSensitiveAppendix}" : Name;
663+
public override bool IsCountOperator() => true;
644664
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
645665
{
646666
return GetCountExpression(left, right, nameof(Expression.GreaterThanOrEqual));
@@ -654,6 +674,7 @@ public HasCountLessThanOrEqualType(bool caseInsensitive = false, bool usesAll =
654674
}
655675

656676
public override string Operator() => CaseInsensitive ? $"{Name}{CaseSensitiveAppendix}" : Name;
677+
public override bool IsCountOperator() => true;
657678
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
658679
{
659680
return GetCountExpression(left, right, nameof(Expression.LessThanOrEqual));
@@ -667,6 +688,7 @@ public HasType(bool caseInsensitive = false, bool usesAll = false) : base("^$",
667688
}
668689

669690
public override string Operator() => CaseInsensitive ? $"{Name}{CaseSensitiveAppendix}" : Name;
691+
public override bool IsCountOperator() => false;
670692
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
671693
{
672694
if (left.Type.IsGenericType &&
@@ -689,6 +711,7 @@ public DoesNotHaveType(bool caseInsensitive = false, bool usesAll = false) : bas
689711
}
690712

691713
public override string Operator() => CaseInsensitive ? $"{Name}{CaseSensitiveAppendix}" : Name;
714+
public override bool IsCountOperator() => false;
692715
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
693716
{
694717
if (left.Type.IsGenericType &&
@@ -711,6 +734,7 @@ public NotInType(bool caseInsensitive = false, bool usesAll = false) : base("!^^
711734
}
712735

713736
public override string Operator() => CaseInsensitive ? $"{Name}{CaseSensitiveAppendix}" : Name;
737+
public override bool IsCountOperator() => false;
714738
public override Expression GetExpression<T>(Expression left, Expression right, Type? dbContextType)
715739
{
716740
var leftType = left.Type;

0 commit comments

Comments
 (0)