Kaynağa Gözat

Added Filter.Match

Kenric Nugteren 6 ay önce
ebeveyn
işleme
6d21a7d599

+ 10 - 0
InABox.Core/CoreUtils.cs

@@ -2363,6 +2363,16 @@ namespace InABox.Core
             return dt.AddDays(-1 * diff).Date;
         }
 
+        public static DateTime StartOfMonth(this DateTime dt)
+        {
+            return new DateTime(dt.Year, dt.Month, 1);
+        }
+
+        public static DateTime StartOfYear(this DateTime dt)
+        {
+            return new DateTime(dt.Year, 1, 1);
+        }
+
         #region String Utilities
 
         /// <summary>

+ 186 - 6
InABox.Core/Query/Filter.cs

@@ -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

+ 20 - 0
InABox.Database/IProvider.cs

@@ -1,4 +1,5 @@
 using InABox.Core;
+using IQueryProvider = InABox.Core.IQueryProvider;
 
 namespace InABox.Database;
 
@@ -243,4 +244,23 @@ public static class ProviderExtensions
     {
         return new ProviderQueryProvider<TEntity>(store.Provider, store.UserID);
     }
+
+    private class ProviderQueryProviderFactory(IProvider provider, string userID) : IQueryProviderFactory
+    {
+        public bool ExcludeCustomProperties => false;
+
+        public IQueryProvider Create(Type T)
+        {
+            return (Activator.CreateInstance(typeof(ProviderQueryProvider<>).MakeGenericType(T), provider, userID) as IQueryProvider)!;
+        }
+    }
+
+    public static IQueryProviderFactory GetQueryProviderFactory(this IProvider provider, string userID)
+    {
+        return new ProviderQueryProviderFactory(provider, userID);
+    }
+    public static IQueryProviderFactory GetQueryProviderFactory(this IStore store)
+    {
+        return new ProviderQueryProviderFactory(store.Provider, store.UserID);
+    }
 }

+ 15 - 0
inabox.wpf/DynamicGrid/Editors/FilterEditor/FilterEditorWindow.xaml.cs

@@ -42,5 +42,20 @@ namespace InABox.DynamicGrid
             DialogResult = true;
             Close();
         }
+
+        public static bool Execute<T>(ref Filter<T>? filter, FilterEditorConfiguration? config = null)
+        {
+            var window = new FilterEditorWindow(configuration: config);
+            window.SetFilter(filter ?? new());
+            if(window.ShowDialog() == true)
+            {
+                filter = window.GetFilter<T>();
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
     }
 }