Skip to content

Commit

Permalink
More changes
Browse files Browse the repository at this point in the history
  • Loading branch information
ejsmith committed Oct 17, 2024
1 parent b0d3b39 commit fec3852
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 18 deletions.
19 changes: 10 additions & 9 deletions src/Foundatio.Parsers.SqlQueries/Extensions/SqlNodeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,26 +128,27 @@ public static string ToDynamicLinqString(this TermNode node, ISqlQueryVisitorCon
return String.Empty;
}

var fieldTerms = new Dictionary<EntityFieldInfo, List<string>>();
var fieldTerms = new Dictionary<EntityFieldInfo, (List<string> Tokens, SqlSearchOperator Operator)>();
foreach (string df in context.DefaultFields)
{
var fieldInfo = GetFieldInfo(context.Fields, df);
if (!fieldTerms.TryGetValue(fieldInfo, out var fields))
if (!fieldTerms.TryGetValue(fieldInfo, out var searchInfo))
{
fields = new List<string>();
fieldTerms.Add(fieldInfo, fields);
searchInfo = (new List<string>(), SqlSearchOperator.Contains);
fieldTerms.Add(fieldInfo, searchInfo);
}

foreach (string token in context.Tokenizer?.Invoke(node.Term) ?? [node.Term])
fields.Add(token);
var result = context.Tokenizer.Invoke(fieldInfo, node.Term);
searchInfo.Tokens.AddRange(result.Tokens);
searchInfo.Operator = result.Operator;
}

var keys = fieldTerms.Keys.ToArray();
for (int index = 0; index < keys.Length; index++)
{
builder.Append(index == 0 ? "(" : " OR ");
var fieldInfo = keys[index];
var terms = fieldTerms[fieldInfo];
var searchInfo = fieldTerms[fieldInfo];

if (fieldInfo.IsCollection)
{
Expand All @@ -159,13 +160,13 @@ public static string ToDynamicLinqString(this TermNode node, ISqlQueryVisitorCon
builder.Append(".Any(");
builder.Append(fieldName);
builder.Append(" in (");
builder.Append(String.Join(',', terms.Select(t => "\"" + t + "\"")));
builder.Append(String.Join(',', searchInfo.Tokens.Select(t => "\"" + t + "\"")));
builder.Append("))");
}
else
{
builder.Append(fieldInfo.Field).Append(" in (");
builder.Append(String.Join(',', terms.Select(t => "\"" + t + "\"")));
builder.Append(String.Join(',', searchInfo.Tokens.Select(t => "\"" + t + "\"")));
builder.Append(")");
}

Expand Down
2 changes: 1 addition & 1 deletion src/Foundatio.Parsers.SqlQueries/SqlQueryParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ private void SetupQueryVisitorContextDefaults(IQueryVisitorContext context)

if (context is ISqlQueryVisitorContext sqlContext)
{
sqlContext.Tokenizer = Configuration.Tokenizer;
sqlContext.Tokenizer = Configuration.SearchTokenizer;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Threading.Tasks;
using Foundatio.Parsers.LuceneQueries;
using Foundatio.Parsers.LuceneQueries.Visitors;
using Foundatio.Parsers.SqlQueries.Visitors;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
Expand All @@ -25,7 +26,7 @@ public SqlQueryParserConfiguration()

public int MaxFieldDepth { get; private set; } = 10;
public QueryFieldResolver FieldResolver { get; private set; }
public Func<string, string[]> Tokenizer { get; set; } = static s => [s];
public Func<EntityFieldInfo, string, SearchTokenizeResult> SearchTokenizer { get; set; } = static (_, t) => new SearchTokenizeResult([t], SqlSearchOperator.Contains);
public EntityTypePropertyFilter EntityTypePropertyFilter { get; private set; } = static _ => true;
public EntityTypeNavigationFilter EntityTypeNavigationFilter { get; private set; } = static _ => true;
public EntityTypeSkipNavigationFilter EntityTypeSkipNavigationFilter { get; private set; } = static _ => true;
Expand All @@ -49,9 +50,9 @@ public SqlQueryParserConfiguration SetDefaultFields(string[] fields)
return this;
}

public SqlQueryParserConfiguration SetTokenizer(Func<string, string[]> tokenizer)
public SqlQueryParserConfiguration SetSearchTokenizer(Func<EntityFieldInfo, string, SearchTokenizeResult> tokenizer)
{
Tokenizer = tokenizer;
SearchTokenizer = tokenizer;
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ namespace Foundatio.Parsers.SqlQueries.Visitors;
public interface ISqlQueryVisitorContext : IQueryVisitorContext
{
List<EntityFieldInfo> Fields { get; set; }
Func<string, string[]> Tokenizer { get; set; }
Func<EntityFieldInfo, string, SearchTokenizeResult> Tokenizer { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,37 @@ namespace Foundatio.Parsers.SqlQueries.Visitors;
public class SqlQueryVisitorContext : QueryVisitorContext, ISqlQueryVisitorContext
{
public List<EntityFieldInfo> Fields { get; set; }
public Func<string, string[]> Tokenizer { get; set; } = s => [s];
public Func<EntityFieldInfo, string, SearchTokenizeResult> Tokenizer { get; set; } = static (_, t) => new SearchTokenizeResult([t], SqlSearchOperator.Contains);
public IEntityType EntityType { get; set; }
}

[DebuggerDisplay("{Field} IsNumber: {IsNumber} IsMoney: {IsMoney} IsDate: {IsDate} IsBoolean: {IsBoolean} IsCollection: {IsCollection}")]
public class EntityFieldInfo
{
protected bool Equals(EntityFieldInfo other) => Field == other.Field;

public override bool Equals(object obj)
{
if (obj is null)
{
return false;
}

if (ReferenceEquals(this, obj))
{
return true;
}

if (obj.GetType() != GetType())
{
return false;
}

return Equals((EntityFieldInfo)obj);
}

public override int GetHashCode() => (Field != null ? Field.GetHashCode() : 0);

public string Field { get; set; }
public bool IsNumber { get; set; }
public bool IsMoney { get; set; }
Expand All @@ -24,3 +48,6 @@ public class EntityFieldInfo
public bool IsCollection { get; set; }
public IDictionary<string, object> Data { get; set; } = new Dictionary<string, object>();
}

public record SearchTokenizeResult(string[] Tokens, SqlSearchOperator Operator);
public enum SqlSearchOperator { Equals, Contains }
32 changes: 29 additions & 3 deletions tests/Foundatio.Parsers.SqlQueries.Tests/SqlQueryParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,23 +86,41 @@ public async Task CanSearchWithTokenizer()
await using var db = await GetSampleContextWithDataAsync(sp);
var parser = sp.GetRequiredService<SqlQueryParser>();
parser.Configuration.SetDefaultFields(["SearchValues.Term"]);
parser.Configuration.SetTokenizer(t =>
parser.Configuration.SetSearchTokenizer((f, t) =>
{
if (f.Field != "SearchValues.Term")
return new SearchTokenizeResult([t], SqlSearchOperator.Contains);

string[] terms = [t.Replace("-", "")];
return terms.Distinct().ToArray();
return new SearchTokenizeResult(terms.Distinct().ToArray(), SqlSearchOperator.Equals);
});

var context = parser.GetContext(db.Employees.EntityType);

string sqlExpected = db.Employees.Where(e => e.SearchValues.Any(s => s.Term == "2142222222")).ToQueryString();
string sqlActual = db.Employees.Where("""SearchValues.Any(Term in ("2142222222"))""").ToQueryString();
Assert.Equal(sqlExpected, sqlActual);

string sql = await parser.ToDynamicLinqAsync("214-222-2222", context);
_logger.LogInformation(sql);
sqlActual = db.Employees.Where(sql).ToQueryString();
var results = await db.Employees.Where(sql).ToListAsync();
Assert.Single(results);
Assert.Equal(sqlExpected, sqlActual);

sql = await parser.ToDynamicLinqAsync("2142222222", context);
_logger.LogInformation(sql);
sqlActual = db.Employees.Where(sql).ToQueryString();
results = await db.Employees.Where(sql).ToListAsync();
Assert.Single(results);
Assert.Equal(sqlExpected, sqlActual);

sql = await parser.ToDynamicLinqAsync("(214) 222-2222", context);
_logger.LogInformation(sql);
sqlActual = db.Employees.Where(sql).ToQueryString();
results = await db.Employees.Where(sql).ToListAsync();
Assert.Single(results);
Assert.Equal(sqlExpected, sqlActual);
}

[Fact]
Expand Down Expand Up @@ -325,9 +343,17 @@ public async Task<SampleContext> GetSampleContextWithDataAsync(IServiceProvider
{
FullName = "Jane Doe",
Title = "Software Developer",
PhoneNumber = "214-333-1111",
Salary = 90_000,
DataValues = [new() { Definition = company.DataDefinitions[0], NumberValue = 23 }],
Companies = [company]
Companies = [company],
SearchValues = [
new() { Term = "jane" },
new() { Term = "doe" },
new() { Term = "software" },
new() { Term = "developer" },
new() { Term = "2143331111" }
]
});
await db.SaveChangesAsync();

Expand Down

0 comments on commit fec3852

Please sign in to comment.