Skip to content

Commit

Permalink
More refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
ejsmith committed Mar 15, 2024
1 parent d38cc54 commit 51e62b0
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -1,28 +1,15 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Reflection;
using Foundatio.Parsers.LuceneQueries;
using Foundatio.Parsers.LuceneQueries.Visitors;
using Foundatio.Parsers.SqlQueries.Extensions;
using Foundatio.Parsers.SqlQueries.Visitors;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.Internal;
using FieldInfo = Foundatio.Parsers.SqlQueries.Visitors.FieldInfo;

namespace Foundatio.Parsers.SqlQueries;

public static class QueryableExtensions
{
private static readonly ConcurrentDictionary<IEntityType, List<FieldInfo>> _entityFieldCache = new();

public static DbContext GetDbContext(IQueryable query)
{
var fi = query.GetType().GetField("_context", BindingFlags.NonPublic | BindingFlags.Instance);
Expand Down Expand Up @@ -56,59 +43,6 @@ public static IQueryable<T> LuceneWhere<T>(this IQueryable<T> source, string que

public static IQueryable<T> LuceneWhere<T>(this IQueryable<T> source, string query, IEntityType entityType, SqlQueryParser parser) where T : class
{
var fields = _entityFieldCache.GetOrAdd(entityType, e =>
{
var fields = new List<FieldInfo>();
AddFields(fields, e);

var dynamicFields = parser.Configuration.EntityTypeDynamicFieldResolver?.Invoke(e) ?? [];
fields.AddRange(dynamicFields);

return fields;
});
var validationOptions = new QueryValidationOptions();
foreach (string field in fields.Select(f => f.Field))
validationOptions.AllowedFields.Add(field);

parser.Configuration.SetValidationOptions(validationOptions);
var context = new SqlQueryVisitorContext { Fields = fields };
var node = parser.Parse(query, context);
var result = ValidationVisitor.Run(node, context);
if (!result.IsValid)
throw new ValidationException("Invalid query: " + result.Message);

string sql = GenerateSqlVisitor.Run(node, context);
return source.Where(sql);
}

private static void AddFields(List<FieldInfo> fields, IEntityType entityType, List<IEntityType> visited = null, string prefix = null)
{
visited ??= [];
if (visited.Contains(entityType))
return;

prefix ??= "";

visited.Add(entityType);

foreach (var property in entityType.GetProperties())
{
if (property.IsIndex() || property.IsKey())
fields.Add(new FieldInfo
{
Field = prefix + property.Name,
IsNumber = property.ClrType.UnwrapNullable().IsNumeric(),
IsDate = property.ClrType.UnwrapNullable().IsDateTime(),
IsBoolean = property.ClrType.UnwrapNullable().IsBoolean()
});
}

foreach (var nav in entityType.GetNavigations())
{
if (visited.Contains(nav.TargetEntityType))
continue;

AddFields(fields, nav.TargetEntityType, visited, prefix + nav.Name + ".");
}
return source.Where(parser.ToDynamicSql(query, entityType));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,13 @@ public static string ToSqlString(this MissingNode node, ISqlQueryVisitorContext
return builder.ToString();
}

public static FieldInfo GetFieldInfo(List<FieldInfo> fields, string field)
public static EntityFieldInfo GetFieldInfo(List<EntityFieldInfo> fields, string field)
{
if (fields == null)
return new FieldInfo { Field = field };
return new EntityFieldInfo { Field = field };

return fields.FirstOrDefault(f => f.Field.Equals(field, StringComparison.OrdinalIgnoreCase)) ??
new FieldInfo { Field = field };
new EntityFieldInfo { Field = field };
}

public static string ToSqlString(this TermNode node, ISqlQueryVisitorContext context)
Expand Down
63 changes: 63 additions & 0 deletions src/Foundatio.Parsers.SqlQueries/SqlQueryParser.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Foundatio.Parsers.LuceneQueries;
using Foundatio.Parsers.LuceneQueries.Extensions;
using Foundatio.Parsers.LuceneQueries.Nodes;
using Foundatio.Parsers.LuceneQueries.Visitors;
using Foundatio.Parsers.SqlQueries.Extensions;
using Foundatio.Parsers.SqlQueries.Visitors;
using Microsoft.EntityFrameworkCore.Metadata;
using Pegasus.Common;

namespace Foundatio.Parsers.SqlQueries;
Expand Down Expand Up @@ -49,6 +55,63 @@ public override async Task<IQueryNode> ParseAsync(string query, IQueryVisitorCon
}
}

private static readonly ConcurrentDictionary<IEntityType, List<EntityFieldInfo>> _entityFieldCache = new();
public string ToDynamicSql(string filter, IEntityType entityType) {
var fields = _entityFieldCache.GetOrAdd(entityType, e =>
{
var fields = new List<EntityFieldInfo>();
AddFields(fields, e);

var dynamicFields = Configuration.EntityTypeDynamicFieldResolver?.Invoke(e) ?? [];
fields.AddRange(dynamicFields);

return fields;
});
var validationOptions = new QueryValidationOptions();
foreach (string field in fields.Select(f => f.Field))
validationOptions.AllowedFields.Add(field);

Configuration.SetValidationOptions(validationOptions);
var context = new SqlQueryVisitorContext { Fields = fields };
var node = Parse(filter, context);
var result = ValidationVisitor.Run(node, context);
if (!result.IsValid)
throw new ValidationException("Invalid query: " + result.Message);

return GenerateSqlVisitor.Run(node, context);
}

private static void AddFields(List<EntityFieldInfo> fields, IEntityType entityType, List<IEntityType> visited = null, string prefix = null)
{
visited ??= [];
if (visited.Contains(entityType))
return;

prefix ??= "";

visited.Add(entityType);

foreach (var property in entityType.GetProperties())
{
if (property.IsIndex() || property.IsKey())
fields.Add(new EntityFieldInfo
{
Field = prefix + property.Name,
IsNumber = property.ClrType.UnwrapNullable().IsNumeric(),
IsDate = property.ClrType.UnwrapNullable().IsDateTime(),
IsBoolean = property.ClrType.UnwrapNullable().IsBoolean()
});
}

foreach (var nav in entityType.GetNavigations())
{
if (visited.Contains(nav.TargetEntityType))
continue;

AddFields(fields, nav.TargetEntityType, visited, prefix + nav.Name + ".");
}
}

private void SetupQueryVisitorContextDefaults(IQueryVisitorContext context)
{
//context.SetMappingResolver(Configuration.MappingResolver);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,4 +228,4 @@ public SqlQueryParserConfiguration AddAggregationVisitorAfter<T>(IChainableQuery
#endregion
}

public delegate List<FieldInfo> EntityTypeDynamicFieldsResolver(IEntityType entityType);
public delegate List<EntityFieldInfo> EntityTypeDynamicFieldsResolver(IEntityType entityType);
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@

namespace Foundatio.Parsers.SqlQueries.Visitors {
public interface ISqlQueryVisitorContext : IQueryVisitorContext {
List<FieldInfo> Fields { get; set; }
List<EntityFieldInfo> Fields { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
namespace Foundatio.Parsers.SqlQueries.Visitors;

public class SqlQueryVisitorContext : QueryVisitorContext, ISqlQueryVisitorContext {
public List<FieldInfo> Fields { get; set; }
public List<EntityFieldInfo> Fields { get; set; }
}

[DebuggerDisplay("{Field} IsNumber: {IsNumber} IsDate: {IsDate} IsBoolean: {IsBoolean} Children: {Children?.Count}")]
public class FieldInfo
public class EntityFieldInfo
{
public string Field { get; set; }
public bool IsNumber { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ public async Task CanGenerateSql()
parser.Configuration.AddQueryVisitor(new DynamicFieldVisitor());
parser.Configuration.UseEntityTypeDynamicFieldResolver(entityType =>
{
var dynamicFields = new List<FieldInfo>();
var dynamicFields = new List<EntityFieldInfo>();
if (entityType.ClrType == typeof(Employee))
{
dynamicFields.Add(new FieldInfo { Field = "age", IsNumber = true, Data = {{ "DataDefinitionId", 1 }}});
dynamicFields.Add(new EntityFieldInfo { Field = "age", IsNumber = true, Data = {{ "DataDefinitionId", 1 }}});
}
return dynamicFields;
});
Expand Down

0 comments on commit 51e62b0

Please sign in to comment.