|
@@ -64,6 +64,8 @@ namespace InABox.Core
|
|
|
}
|
|
|
|
|
|
|
|
|
+ // !!! If updating this list, make sure to update everywhere this list is used! That includes SQLiteProvider, the ToString() functions,
|
|
|
+ // the FilterExtensions.MatchOperator, but possible otherwhere to; make sure everything is updated.
|
|
|
public enum Operator
|
|
|
{
|
|
|
IsEqualTo,
|
|
@@ -84,13 +86,15 @@ namespace InABox.Core
|
|
|
Not = byte.MaxValue
|
|
|
}
|
|
|
|
|
|
-
|
|
|
public enum ListOperator
|
|
|
{
|
|
|
Includes,
|
|
|
Excludes
|
|
|
}
|
|
|
|
|
|
+ // !!! If updating this list, make sure to update everywhere this list is
|
|
|
+ // used! That includes SQLiteProvider the FilterConstants.Resolve,
|
|
|
+ // but possible otherwhere to; make sure everything is updated.
|
|
|
public enum FilterConstant
|
|
|
{
|
|
|
Today,
|
|
@@ -201,6 +205,52 @@ namespace InABox.Core
|
|
|
|
|
|
return new FilterConstant[] { };
|
|
|
}
|
|
|
+
|
|
|
+ public static object? Resolve(this FilterConstant constant, Type type)
|
|
|
+ {
|
|
|
+ return constant switch
|
|
|
+ {
|
|
|
+ FilterConstant.Null => CoreUtils.GetDefault(type),
|
|
|
+ FilterConstant.Today => DateTime.Today,
|
|
|
+ FilterConstant.Now => DateTime.Now,
|
|
|
+ FilterConstant.Zero => CoreUtils.GetDefault(type),
|
|
|
+ FilterConstant.OneWeekAgo => DateTime.Today.AddDays(-7),
|
|
|
+ FilterConstant.OneWeekAhead => DateTime.Today.AddDays(7),
|
|
|
+ FilterConstant.OneMonthAgo => DateTime.Today.AddMonths(-1),
|
|
|
+ FilterConstant.OneMonthAhead => DateTime.Today.AddMonths(1),
|
|
|
+ FilterConstant.ThreeMonthsAgo => DateTime.Today.AddMonths(-3),
|
|
|
+ FilterConstant.ThreeMonthsAhead => DateTime.Today.AddMonths(3),
|
|
|
+ FilterConstant.SixMonthsAgo => DateTime.Today.AddMonths(-6),
|
|
|
+ FilterConstant.SixMonthsAhead => DateTime.Today.AddMonths(6),
|
|
|
+ FilterConstant.OneYearAgo => DateTime.Today.AddYears(-1),
|
|
|
+ FilterConstant.OneYearAhead => DateTime.Today.AddYears(1),
|
|
|
+ FilterConstant.StartOfLastWeek => DateTime.Today.StartOfWeek(DayOfWeek.Monday).AddDays(-7),
|
|
|
+ FilterConstant.EndOfLastWeek => DateTime.Today.StartOfWeek(DayOfWeek.Monday).AddTicks(-1),
|
|
|
+ FilterConstant.StartOfThisWeek => DateTime.Today.StartOfWeek(DayOfWeek.Monday),
|
|
|
+ FilterConstant.EndOfThisWeek => DateTime.Today.StartOfWeek(DayOfWeek.Monday).AddDays(7).AddTicks(-1),
|
|
|
+ FilterConstant.StartOfNextWeek => DateTime.Today.StartOfWeek(DayOfWeek.Monday).AddDays(7),
|
|
|
+ FilterConstant.EndOfNextWeek => DateTime.Today.StartOfWeek(DayOfWeek.Monday).AddDays(14).AddTicks(-1),
|
|
|
+ FilterConstant.StartOfLastMonth => DateTime.Today.StartOfMonth().AddMonths(-1),
|
|
|
+ FilterConstant.EndOfLastMonth => DateTime.Today.StartOfMonth().AddTicks(-1),
|
|
|
+ FilterConstant.StartOfThisMonth => DateTime.Today.StartOfMonth(),
|
|
|
+ FilterConstant.EndOfThisMonth => DateTime.Today.StartOfMonth().AddMonths(1).AddTicks(-1),
|
|
|
+ FilterConstant.StartOfNextMonth => DateTime.Today.StartOfMonth().AddMonths(1),
|
|
|
+ FilterConstant.EndOfNextMonth => DateTime.Today.StartOfMonth().AddMonths(2).AddTicks(-1),
|
|
|
+ FilterConstant.StartOfLastCalendarYear => DateTime.Today.StartOfYear().AddYears(-1),
|
|
|
+ FilterConstant.EndOfLastCalendarYear => DateTime.Today.StartOfYear().AddTicks(-1),
|
|
|
+ FilterConstant.StartOfThisCalendarYear => DateTime.Today.StartOfYear(),
|
|
|
+ FilterConstant.EndOfThisCalendarYear => DateTime.Today.StartOfYear().AddYears(1).AddTicks(-1),
|
|
|
+ FilterConstant.StartOfNextCalendarYear => DateTime.Today.StartOfYear().AddYears(1),
|
|
|
+ FilterConstant.EndOfNextCalendarYear => DateTime.Today.StartOfYear().AddYears(2).AddTicks(-1),
|
|
|
+ FilterConstant.StartOfLastFinancialYear => DateTime.Today.AddMonths(6).StartOfYear().AddMonths(-18),
|
|
|
+ FilterConstant.EndOfLastFinancialYear => DateTime.Today.AddMonths(6).StartOfYear().AddMonths(-6).AddTicks(-1),
|
|
|
+ FilterConstant.StartOfThisFinancialYear => DateTime.Today.AddMonths(6).StartOfYear().AddMonths(-6),
|
|
|
+ FilterConstant.EndOfThisFinancialYear => DateTime.Today.AddMonths(6).StartOfYear().AddMonths(6).AddTicks(-1),
|
|
|
+ FilterConstant.StartOfNextFinancialYear => DateTime.Today.AddMonths(6).StartOfYear().AddMonths(6),
|
|
|
+ FilterConstant.EndOfNextFinancialYear => DateTime.Today.AddMonths(6).StartOfYear().AddMonths(18).AddTicks(-1),
|
|
|
+ _ => throw new Exception($"Cannot handle {constant}!"),
|
|
|
+ };
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
public class CustomFilterValue
|
|
@@ -228,7 +278,13 @@ namespace InABox.Core
|
|
|
|
|
|
bool IsNot { get; set; }
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// List of filters to apply to the main filter with boolean operator AND. The <see cref="Ands"/> take precedence over the <see cref="Ors"/>.
|
|
|
+ /// </summary>
|
|
|
IEnumerable<IFilter> Ands { get; }
|
|
|
+ /// <summary>
|
|
|
+ /// List of filters to apply to the main filter with boolean operator OR. The <see cref="Ands"/> take precedence over the <see cref="Ors"/>.
|
|
|
+ /// </summary>
|
|
|
IEnumerable<IFilter> Ors { get; }
|
|
|
|
|
|
public IFilter And(IFilter filter);
|
|
@@ -378,11 +434,6 @@ namespace InABox.Core
|
|
|
public static Filter<T> None<T>() => new Filter<T>().None();
|
|
|
}
|
|
|
|
|
|
- public interface IFilter2<T>
|
|
|
- {
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
public class Filter<T> : IFilter
|
|
|
{
|
|
|
public Type Type => PropertyDefinition?.PropertyType ?? typeof(object);
|
|
@@ -425,8 +476,14 @@ namespace InABox.Core
|
|
|
|
|
|
public Filter<T>? Parent { get; set; }
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// List of filters to apply to the main filter with boolean operator AND. The <see cref="Ands"/> take precedence over the <see cref="Ors"/>.
|
|
|
+ /// </summary>
|
|
|
public List<Filter<T>> Ands { get; private set; }
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// List of filters to apply to the main filter with boolean operator OR. The <see cref="Ands"/> take precedence over the <see cref="Ors"/>.
|
|
|
+ /// </summary>
|
|
|
public List<Filter<T>> Ors { get; private set; }
|
|
|
|
|
|
public Operator Operator { get; set; }
|
|
@@ -1901,6 +1958,129 @@ namespace InABox.Core
|
|
|
return filter1.And(filter2);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ private static object? Encode(Type T, object? value)
|
|
|
+ {
|
|
|
+ if(T == typeof(string) && Equals(value, null))
|
|
|
+ {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool MatchOperator<T>(this Filter<T> filter, T obj, IQueryProviderFactory query)
|
|
|
+ {
|
|
|
+ if(filter.Operator == Operator.All)
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ else if(filter.Operator == Operator.None)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (filter.PropertyDefinition is null) return false;
|
|
|
+
|
|
|
+ if(filter.Value is CustomFilterValue)
|
|
|
+ {
|
|
|
+ throw new Exception("Custom Filter Value not able to be processed!");
|
|
|
+ }
|
|
|
+
|
|
|
+ var valueType = filter.PropertyDefinition.PropertyType;
|
|
|
+ var objValue = Encode(valueType, filter.PropertyDefinition.Getter()(obj));
|
|
|
+ if(filter.Operator == Operator.InList || filter.Operator == Operator.NotInList)
|
|
|
+ {
|
|
|
+ if(filter.Value is Array arr && arr.Length == 0)
|
|
|
+ {
|
|
|
+ return filter.Operator != Operator.InList;
|
|
|
+ }
|
|
|
+ else if(filter.Value is IEnumerable enumerable)
|
|
|
+ {
|
|
|
+ foreach(var item in enumerable)
|
|
|
+ {
|
|
|
+ if (Equals(Encode(valueType, item), objValue))
|
|
|
+ return filter.Operator == Operator.InList;
|
|
|
+ }
|
|
|
+ return filter.Operator == Operator.NotInList;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if(filter.Operator == Operator.InQuery || filter.Operator == Operator.NotInQuery)
|
|
|
+ {
|
|
|
+ if(filter.Value is ISubQuery subQuery)
|
|
|
+ {
|
|
|
+ var result = query.Create(subQuery.GetQueryType());
|
|
|
+ var subFilter = Filter.Create(subQuery.GetQueryType(), subQuery.GetColumn().Name, Operator.IsEqualTo, objValue);
|
|
|
+ if(subQuery.GetFilter() is IFilter subQueryFilter)
|
|
|
+ {
|
|
|
+ subFilter.And(subQueryFilter);
|
|
|
+ }
|
|
|
+ var inQuery = result.Query(subFilter, Columns.None(subQuery.GetQueryType()))
|
|
|
+ .Rows.Count > 0;
|
|
|
+ return inQuery ? filter.Operator == Operator.InQuery : filter.Operator == Operator.NotInQuery;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ object? value;
|
|
|
+ if(filter.Value is FilterConstant constant)
|
|
|
+ {
|
|
|
+ value = constant.Resolve(filter.PropertyDefinition.PropertyType);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ value = filter.Value;
|
|
|
+ }
|
|
|
+ value = Encode(filter.PropertyDefinition.PropertyType, value);
|
|
|
+ return filter.Operator switch
|
|
|
+ {
|
|
|
+ Operator.IsEqualTo => Equals(objValue, value),
|
|
|
+ Operator.IsNotEqualTo => !Equals(objValue, value),
|
|
|
+ Operator.IsGreaterThan => TryCompare(objValue, value, out var comparison) && comparison > 0,
|
|
|
+ Operator.IsGreaterThanOrEqualTo => TryCompare(objValue, value, out var comparison) && comparison >= 0,
|
|
|
+ Operator.IsLessThan => TryCompare(objValue, value, out var comparison) && comparison < 0,
|
|
|
+ Operator.IsLessThanOrEqualTo => TryCompare(objValue, value, out var comparison) && comparison <= 0,
|
|
|
+ Operator.BeginsWith => (objValue?.ToString() ?? "").StartsWith(value?.ToString() ?? ""),
|
|
|
+ Operator.Contains => (objValue?.ToString() ?? "").Contains(value?.ToString() ?? ""),
|
|
|
+ Operator.EndsWith => (objValue?.ToString() ?? "").EndsWith(value?.ToString() ?? ""),
|
|
|
+ _ => throw new Exception($"Invalid operator {filter.Operator}"),
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static bool MatchFilter<T>(this Filter<T> filter, T obj, IQueryProviderFactory query) =>
|
|
|
+ filter.IsNot ? !filter.MatchOperator(obj, query) : filter.MatchOperator(obj, query);
|
|
|
+
|
|
|
+
|
|
|
+ private static bool TryCompare(object? a, object? b, out int result)
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ result = Comparer.Default.Compare(a, b);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ catch (ArgumentException)
|
|
|
+ {
|
|
|
+ result = 0;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Check if <paramref name="obj"/> matches this filter; that is, that the operators return true.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="query">The query provider used to evaluate <see cref="Operator.InQuery"/> filters.</param>
|
|
|
+ public static bool Match<T>(this Filter<T>? filter, T obj, IQueryProviderFactory query) =>
|
|
|
+ filter is null
|
|
|
+ || (filter.MatchFilter(obj, query) && filter.Ands.All(x => x.Match(obj, query)))
|
|
|
+ || filter.Ors.Any(x => x.Match(obj, query));
|
|
|
}
|
|
|
|
|
|
public class FilterJsonConverter : JsonConverter
|