From 299a3f128182a764e44a18a57d032f78132f6fe6 Mon Sep 17 00:00:00 2001 From: Luke Gordon Date: Thu, 11 Apr 2024 13:17:17 -0500 Subject: [PATCH] Add checks for navigation collections (#80) * test(sql): add tests for using navigations * fix: correct tests to highlight navigation bug * fix: add check if skip navigation is collection * test: add checks to confirm skip navigations * fix: respect ISkipNavigation#IsCollection --- .../SqlQueryParser.cs | 7 ++- .../SampleContext.cs | 1 + .../SqlQueryParserTests.cs | 50 +++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/Foundatio.Parsers.SqlQueries/SqlQueryParser.cs b/src/Foundatio.Parsers.SqlQueries/SqlQueryParser.cs index 77edc70e..c0e52721 100644 --- a/src/Foundatio.Parsers.SqlQueries/SqlQueryParser.cs +++ b/src/Foundatio.Parsers.SqlQueries/SqlQueryParser.cs @@ -130,7 +130,9 @@ private void AddEntityFields(List fields, IEntityType entityTyp continue; string propertyPath = prefix + nav.Name; - AddEntityFields(fields, nav.TargetEntityType, entityTypeStack, propertyPath + ".", false, depth + 1); + bool isNavCollection = nav is IReadOnlyNavigationBase { IsCollection: true }; + + AddEntityFields(fields, nav.TargetEntityType, entityTypeStack, propertyPath + ".", isNavCollection, depth + 1); } foreach (var skipNav in entityType.GetSkipNavigations()) @@ -139,7 +141,8 @@ private void AddEntityFields(List fields, IEntityType entityTyp continue; string propertyPath = prefix + skipNav.Name; - AddEntityFields(fields, skipNav.TargetEntityType, entityTypeStack, propertyPath + ".", true, depth + 1); + + AddEntityFields(fields, skipNav.TargetEntityType, entityTypeStack, propertyPath + ".", skipNav.IsCollection, depth + 1); } entityTypeStack.Pop(); diff --git a/tests/Foundatio.Parsers.SqlQueries.Tests/SampleContext.cs b/tests/Foundatio.Parsers.SqlQueries.Tests/SampleContext.cs index af679666..47f0c57d 100644 --- a/tests/Foundatio.Parsers.SqlQueries.Tests/SampleContext.cs +++ b/tests/Foundatio.Parsers.SqlQueries.Tests/SampleContext.cs @@ -40,6 +40,7 @@ public class Employee { public int Id { get; set; } public string FullName { get; set; } public string Title { get; set; } + public int Salary { get; set; } public List Companies { get; set; } public List DataValues { get; set; } public DateTime Created { get; set; } = DateTime.Now; diff --git a/tests/Foundatio.Parsers.SqlQueries.Tests/SqlQueryParserTests.cs b/tests/Foundatio.Parsers.SqlQueries.Tests/SqlQueryParserTests.cs index da22ce98..2d807598 100644 --- a/tests/Foundatio.Parsers.SqlQueries.Tests/SqlQueryParserTests.cs +++ b/tests/Foundatio.Parsers.SqlQueries.Tests/SqlQueryParserTests.cs @@ -162,6 +162,54 @@ public async Task CanUseCollectionDefaultFields() Assert.Equal(sqlExpected, sqlActual); } + [Fact] + public async Task CanUseNavigationFields() + { + var sp = GetServiceProvider(); + await using var db = await GetSampleContextWithDataAsync(sp); + var parser = sp.GetRequiredService(); + + var context = parser.GetContext(db.Companies.EntityType); + + Assert.Contains(db.Companies.EntityType.GetNavigations(), e => e.TargetEntityType == db.DataDefinitions.EntityType); + + string sqlExpected = db.Companies.Where(e => e.DataDefinitions.Any(c => c.Key == "age")).ToQueryString(); + string sqlActual = db.Companies.Where("""DataDefinitions.Any(Key.Equals("age"))""").ToQueryString(); + Assert.Equal(sqlExpected, sqlActual); + string sql = await parser.ToDynamicLinqAsync("datadefinitions.key:age", context); + sqlActual = db.Companies.Where(sql).ToQueryString(); + Assert.Equal(sqlExpected, sqlActual); + + var query = db.Companies.AsQueryable(); + var companies = await query.Where(sql).ToListAsync(); + + Assert.Single(companies); + } + + [Fact] + public async Task CanUseSkipNavigationFields() + { + var sp = GetServiceProvider(); + await using var db = await GetSampleContextWithDataAsync(sp); + var parser = sp.GetRequiredService(); + + var context = parser.GetContext(db.Companies.EntityType); + + Assert.Contains(db.Companies.EntityType.GetSkipNavigations(), e => e.TargetEntityType == db.Employees.EntityType); + + string sqlExpected = db.Companies.Where(e => e.Employees.Any(c => c.Salary.Equals(80_000))).ToQueryString(); + string sqlActual = db.Companies.Where("""Employees.Any(Salary.Equals(80000))""").ToQueryString(); + Assert.Equal(sqlExpected, sqlActual); + string sql = await parser.ToDynamicLinqAsync("employees.salary:80000", context); + sqlActual = db.Companies.Where(sql).ToQueryString(); + Assert.Equal(sqlExpected, sqlActual); + + var query = db.Companies.AsQueryable(); + var companies = await query.Where(sql).ToListAsync(); + + Assert.Single(companies); + } + [Fact] public async Task CanGenerateSql() { @@ -231,6 +279,7 @@ public async Task GetSampleContextWithDataAsync(IServiceProvider { FullName = "John Doe", Title = "Software Developer", + Salary = 80_000, DataValues = [ new() { Definition = company.DataDefinitions[0], NumberValue = 30 } ], Companies = [company] }); @@ -238,6 +287,7 @@ public async Task GetSampleContextWithDataAsync(IServiceProvider { FullName = "Jane Doe", Title = "Software Developer", + Salary = 90_000, DataValues = [ new() { Definition = company.DataDefinitions[0], NumberValue = 23 } ], Companies = [company] });