Skip to content

Commit

Permalink
Date math and fixed missing and exists
Browse files Browse the repository at this point in the history
  • Loading branch information
ejsmith committed Mar 21, 2024
1 parent e39c815 commit 4bfd6ae
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 70 deletions.
50 changes: 40 additions & 10 deletions src/Foundatio.Parsers.SqlQueries/Extensions/SqlNodeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ public static string ToDynamicLinqString(this ExistsNode node, ISqlQueryVisitorC

var builder = new StringBuilder();

if (node.IsNegated.HasValue && node.IsNegated.Value)
builder.Append("NOT ");

builder.Append(node.Field);
builder.Append(" IS NOT NULL");
if (!node.IsNegated.HasValue || !node.IsNegated.Value)
builder.Append(" != null");
else
builder.Append(" == null");

return builder.ToString();
}
Expand All @@ -95,11 +95,11 @@ public static string ToDynamicLinqString(this MissingNode node, ISqlQueryVisitor

var builder = new StringBuilder();

if (node.IsNegated.HasValue && node.IsNegated.Value)
builder.Append("NOT ");

builder.Append(node.Field);
builder.Append(" IS NULL");
if (!node.IsNegated.HasValue || !node.IsNegated.Value)
builder.Append(" == null");
else
builder.Append(" != null");

return builder.ToString();
}
Expand Down Expand Up @@ -164,7 +164,7 @@ public static string ToDynamicLinqString(this TermNode node, ISqlQueryVisitorCon
var field = GetFieldInfo(context.Fields, node.Field);

if (node.IsNegated.HasValue && node.IsNegated.Value)
builder.Append("NOT ");
builder.Append("!");

if (field.IsCollection)
{
Expand Down Expand Up @@ -207,7 +207,37 @@ private static void AppendField(StringBuilder builder, EntityFieldInfo field, st
if (field.IsNumber || field.IsBoolean || field.IsMoney)
builder.Append(term);
else if (field is { IsDate: true })
builder.Append("DateTime.Parse(\"" + term + "\")");
{
term = term.Trim();
if (term.StartsWith("now", StringComparison.OrdinalIgnoreCase))
{
builder.Append("DateTime.UtcNow");

if (term.Length == 3)
return;

builder.Append(".");

var method = term[^1..] switch {
"y" => "AddYears",
"M" => "AddMonths",
"d" => "AddDays",
"h" => "AddHours",
"H" => "AddHours",
"m" => "AddMinutes",
"s" => "AddSeconds",
_ => throw new NotSupportedException("Invalid date operation.")
};

var subtract = term.Substring(3, 1) == "-";

builder.Append(method).Append("(").Append(subtract ? "-" : "").Append(term.Substring(4, term.Length - 5)).Append(")");
}
else
{
builder.Append("DateTime.Parse(\"" + term + "\")");
}
}
else
builder.Append("\"" + term + "\"");
}
Expand Down
65 changes: 65 additions & 0 deletions tests/Foundatio.Parsers.SqlQueries.Tests/DynamicFieldVisitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System.Text;
using Foundatio.Parsers.LuceneQueries.Nodes;
using Foundatio.Parsers.LuceneQueries.Visitors;
using Foundatio.Parsers.SqlQueries.Extensions;
using Foundatio.Parsers.SqlQueries.Visitors;

namespace Foundatio.Parsers.SqlQueries.Tests;

public class DynamicFieldVisitor : ChainableMutatingQueryVisitor
{
public override IQueryNode Visit(TermNode node, IQueryVisitorContext context)
{
if (context is not SqlQueryVisitorContext sqlContext)
return node;

var field = SqlNodeExtensions.GetFieldInfo(sqlContext.Fields, node.Field);

if (field == null || !field.Data.TryGetValue("DataDefinitionId", out object value) ||
value is not int dataDefinitionId)
{
return node;
}

var customFieldBuilder = new StringBuilder();

customFieldBuilder.Append("DataValues.Any(DataDefinitionId = ");
customFieldBuilder.Append(dataDefinitionId);
customFieldBuilder.Append(" AND ");
switch (field)
{
case { IsMoney: true }:
customFieldBuilder.Append("MoneyValue");
break;
case { IsNumber: true }:
customFieldBuilder.Append("NumberValue");
break;
case { IsBoolean: true }:
customFieldBuilder.Append("BooleanValue");
break;
case { IsDate: true }:
customFieldBuilder.Append("DateValue");
break;
default:
customFieldBuilder.Append("StringValue");
break;
}

customFieldBuilder.Append(" = ");
if (field is { IsNumber: true } or { IsBoolean: true })
{
customFieldBuilder.Append(node.Term);
}
else
{
customFieldBuilder.Append("\"");
customFieldBuilder.Append(node.Term);
customFieldBuilder.Append("\"");
}
customFieldBuilder.Append(")");

node.SetQuery(customFieldBuilder.ToString());

return node;
}
}
111 changes: 51 additions & 60 deletions tests/Foundatio.Parsers.SqlQueries.Tests/SqlQueryParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Text;
using System.Threading.Tasks;
using Foundatio.Parsers.LuceneQueries.Nodes;
using Foundatio.Parsers.LuceneQueries.Visitors;
using Foundatio.Parsers.SqlQueries.Extensions;
using Foundatio.Parsers.SqlQueries.Visitors;
using Foundatio.Xunit;
using Microsoft.EntityFrameworkCore;
Expand Down Expand Up @@ -95,6 +93,57 @@ public async Task CanUseDateFilter()
Assert.Equal(sqlExpected, sqlActual);
}

[Fact]
public async Task CanUseExistsFilter()
{
var sp = GetServiceProvider();
await using var db = await GetSampleContextWithDataAsync(sp);
var parser = sp.GetRequiredService<SqlQueryParser>();

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

string sqlExpected = db.Employees.Where(e => e.Title != null).ToQueryString();
string sqlActual = db.Employees.Where("""Title != null""").ToQueryString();
Assert.Equal(sqlExpected, sqlActual);
string sql = await parser.ToDynamicLinqAsync("_exists_:title", context);
sqlActual = db.Employees.Where(sql).ToQueryString();
Assert.Equal(sqlExpected, sqlActual);
}

[Fact]
public async Task CanUseMissingFilter()
{
var sp = GetServiceProvider();
await using var db = await GetSampleContextWithDataAsync(sp);
var parser = sp.GetRequiredService<SqlQueryParser>();

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

string sqlExpected = db.Employees.Where(e => e.Title == null).ToQueryString();
string sqlActual = db.Employees.Where("""Title == null""").ToQueryString();
Assert.Equal(sqlExpected, sqlActual);
string sql = await parser.ToDynamicLinqAsync("_missing_:title", context);
sqlActual = db.Employees.Where(sql).ToQueryString();
Assert.Equal(sqlExpected, sqlActual);
}

[Fact]
public async Task CanUseDateMathFilter()
{
var sp = GetServiceProvider();
await using var db = await GetSampleContextWithDataAsync(sp);
var parser = sp.GetRequiredService<SqlQueryParser>();

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

string sqlExpected = db.Employees.Where(e => e.Created > DateTime.UtcNow.AddDays(-90)).ToQueryString();
string sqlActual = db.Employees.Where("""created > DateTime.UtcNow.AddDays(-90)""").ToQueryString();
Assert.Equal(sqlExpected, sqlActual);
string sql = await parser.ToDynamicLinqAsync("created:>now-90d", context);
sqlActual = db.Employees.Where(sql).ToQueryString();
Assert.Equal(sqlExpected, sqlActual);
}

[Fact]
public async Task CanUseCollectionDefaultFields()
{
Expand Down Expand Up @@ -231,61 +280,3 @@ private async Task ParseAndValidateQuery(string query, string expected, bool isV
Assert.Equal(expected, generatedQuery);
}
}

public class DynamicFieldVisitor : ChainableMutatingQueryVisitor
{
public override IQueryNode Visit(TermNode node, IQueryVisitorContext context)
{
if (context is not SqlQueryVisitorContext sqlContext)
return node;

var field = SqlNodeExtensions.GetFieldInfo(sqlContext.Fields, node.Field);

if (field == null || !field.Data.TryGetValue("DataDefinitionId", out object value) ||
value is not int dataDefinitionId)
{
return node;
}

var customFieldBuilder = new StringBuilder();

customFieldBuilder.Append("DataValues.Any(DataDefinitionId = ");
customFieldBuilder.Append(dataDefinitionId);
customFieldBuilder.Append(" AND ");
switch (field)
{
case { IsMoney: true }:
customFieldBuilder.Append("MoneyValue");
break;
case { IsNumber: true }:
customFieldBuilder.Append("NumberValue");
break;
case { IsBoolean: true }:
customFieldBuilder.Append("BooleanValue");
break;
case { IsDate: true }:
customFieldBuilder.Append("DateValue");
break;
default:
customFieldBuilder.Append("StringValue");
break;
}

customFieldBuilder.Append(" = ");
if (field is { IsNumber: true } or { IsBoolean: true })
{
customFieldBuilder.Append(node.Term);
}
else
{
customFieldBuilder.Append("\"");
customFieldBuilder.Append(node.Term);
customFieldBuilder.Append("\"");
}
customFieldBuilder.Append(")");

node.SetQuery(customFieldBuilder.ToString());

return node;
}
}

0 comments on commit 4bfd6ae

Please sign in to comment.