diff --git a/docs/articles/filtering.md b/docs/articles/filtering.md
new file mode 100644
index 00000000..51ffc566
--- /dev/null
+++ b/docs/articles/filtering.md
@@ -0,0 +1,215 @@
+---
+title: Filtering
+permalink: /filtering
+uid: filtering
+order: 3
+---
+Filtering
+===
+
+_**Tip**: There are many examples of filtering in the [`FluentApiTests` source code](https://github.com/Shazwazza/Examine/blob/release/3.0/src/Examine.Test/Examine.Lucene/Search/FluentApiTests.cs) to use as examples/reference._
+
+## Common
+
+Obtain an instance of [`ISearcher`](xref:Examine.ISearcher) for the index to be searched from [`IExamineManager`](xref:Examine.IExamineManager).
+
+### Terms and Phrases
+
+When filtering on fields like in the example above you might want to search on more than one word/term. In Examine this can be done by simply adding more terms to the term filter.
+
+### Term Filter
+
+```csharp
+var searcher = myIndex.Searcher;
+var results = searcher.CreateQuery()
+ // Look for any addresses that has "Hills" or "Rockyroad" or "Hollywood"
+ .WithFilter(
+ filter =>
+ {
+ filter.TermFilter(new FilterTerm("Address", "Hills Rockyroad Hollywood"));
+ })
+ .All()
+ .Execute();
+```
+
+### Terms Filter
+
+```csharp
+var searcher = myIndex.Searcher;
+var results = searcher.CreateQuery()
+ // Look for any addresses that has "Hills" or "Rockyroad" or "Hollywood"
+ .WithFilter(
+ filter =>
+ {
+ filter.TermsFilter(new[] {new FilterTerm("Address", "Hills"), new FilterTerm("Address", "Rockyroad"), new FilterTerm("Address", "Hollywood") });
+ })
+ .All()
+ .Execute();
+```
+
+### Term Prefix Filter
+
+```csharp
+var searcher = myIndex.Searcher;
+var results = searcher.CreateQuery()
+ // Look for any addresses that starts with "Hills"
+ .WithFilter(
+ filter =>
+ {
+ filter.TermPrefixFilter(new FilterTerm("Address", "Hills"));
+ })
+ .All()
+ .Execute();
+```
+
+## Range Filters
+
+Range Filters allow one to match documents whose field(s) values are between the lower and upper bound specified by the Range Filter
+
+### Int Range
+
+Example:
+
+```csharp
+var searcher = myIndex.Searcher;
+var query = searcher.CreateQuery();
+ query.WithFilter(
+ filter =>
+ {
+ filter.IntRangeFilter("SomeInt", 0, 100, minInclusive: true, maxInclusive: true);
+ }).All();
+var results = query.Execute(QueryOptions.Default);
+```
+
+This will return results where the field `SomeInt` is within the range 0 - 100 (min value and max value included).
+
+### Long Range
+
+Example:
+
+```csharp
+var searcher = myIndex.Searcher;
+var query = searcher.CreateQuery();
+ query.WithFilter(
+ filter =>
+ {
+ filter.LongRangeFilter("SomeLong", 0, 100, minInclusive: true, maxInclusive: true);
+ }).All();
+var results = query.Execute(QueryOptions.Default);
+```
+
+This will return results where the field `SomeLong` is within the range 0 - 100 (min value and max value included).
+
+### Float Range
+
+Example:
+
+```csharp
+var searcher = myIndex.Searcher;
+var query = searcher.CreateQuery();
+ query.WithFilter(
+ filter =>
+ {
+ filter.FloatRangeFilter("SomeFloat", 0f, 100f, minInclusive: true, maxInclusive: true);
+ }).All();
+var results = query.Execute(QueryOptions.Default);
+```
+
+This will return results where the field `SomeFloat` is within the range 0 - 100 (min value and max value included).
+
+### Double Range
+
+Example:
+
+```csharp
+var searcher = myIndex.Searcher;
+var query = searcher.CreateQuery();
+ query.WithFilter(
+ filter =>
+ {
+ filter.FloatRangeFilter("SomeDouble", 0.0, 100.0, minInclusive: true, maxInclusive: true);
+ }).All();
+var results = query.Execute(QueryOptions.Default);
+```
+
+This will return results where the field `SomeDouble` is within the range 0 - 100 (min value and max value included).
+
+## Booleans
+
+### Or
+
+```csharp
+var searcher = myIndex.Searcher;
+var results = searcher.CreateQuery()
+ // Look for any addresses that start with "Hills" or "Valleys"
+ .WithFilter(
+ filter =>
+ {
+ filter.TermPrefixFilter(new FilterTerm("Address", "Hills"))
+ .OrFilter()
+ filter.TermPrefixFilter(new FilterTerm("Address", "Valleys"));
+ })
+ .All()
+ .Execute();
+```
+
+### And
+
+```csharp
+var searcher = myIndex.Searcher;
+var results = searcher.CreateQuery()
+ // Look for any addresses that has "Hills" and keyword "Examine"
+ .WithFilter(
+ filter =>
+ {
+ filter.TermFilter(new FilterTerm("Address", "Hills"))
+ .AndFilter()
+ filter.TermFilter(new FilterTerm("Keyword", "Examine"));
+ })
+ .All()
+ .Execute();
+```
+
+### Not
+
+```csharp
+var searcher = myIndex.Searcher;
+var results = searcher.CreateQuery()
+ // Look for any addresses that has "Hills" and keyword "Examine"
+ .WithFilter(
+ filter =>
+ {
+ filter.TermFilter(new FilterTerm("Address", "Hills"))
+ .NotFilter()
+ filter.TermFilter(new FilterTerm("Keyword", "Examine"));
+ })
+ .All()
+ .Execute();
+```
+
+### And Not
+
+```csharp
+var searcher = myIndex.Searcher;
+var results = searcher.CreateQuery()
+ // Look for any addresses that has "Hills" and not keyword "Examine"
+ .WithFilter(
+ filter =>
+ {
+ filter.TermFilter(new FilterTerm("Address", "Hills"))
+ .AndNotFilter(innerFilter => innerFilter.TermFilter(new FilterTerm("Keyword", "Examine")));
+ })
+ .All()
+ .Execute();
+```
+
+### Custom lucene filter
+
+```csharp
+var searcher = indexer.Searcher;
+var query = searcher.CreateQuery();
+
+var query = (LuceneSearchQuery)query.NativeQuery("hello:world").And(); // Make query ready for extending
+query.LuceneFilter(new TermFilter(new Term("nodeTypeAlias", "CWS_Home"))); // Add the raw lucene query
+var results = query.Execute();
+```
\ No newline at end of file
diff --git a/docs/articles/toc.yml b/docs/articles/toc.yml
index cbe731e4..ab463b5d 100644
--- a/docs/articles/toc.yml
+++ b/docs/articles/toc.yml
@@ -4,6 +4,8 @@
href: indexing.md
- name: Searching
href: searching.md
+- name: Filtering
+ href: filtering.md
- name: Sorting
href: sorting.md
- name: Paging
diff --git a/src/Examine.Core/FieldDefinitionTypes.cs b/src/Examine.Core/FieldDefinitionTypes.cs
index 5fe13cb3..a6f00b27 100644
--- a/src/Examine.Core/FieldDefinitionTypes.cs
+++ b/src/Examine.Core/FieldDefinitionTypes.cs
@@ -13,10 +13,16 @@ public static class FieldDefinitionTypes
public const string Integer = "int";
///
- /// Will be indexed as a float
+ /// Will be indexed as a single
///
public const string Float = "float";
+ ///
+ /// Will be indexed as a single
+ ///
+ [Obsolete("To remove in Examine V5. Use Float")]
+ public const string Single = "single";
+
///
/// Will be indexed as a double
///
diff --git a/src/Examine.Core/Search/FilterTerm.cs b/src/Examine.Core/Search/FilterTerm.cs
new file mode 100644
index 00000000..79643c68
--- /dev/null
+++ b/src/Examine.Core/Search/FilterTerm.cs
@@ -0,0 +1,30 @@
+namespace Examine.Search
+{
+ ///
+ /// Term
+ ///
+ public struct FilterTerm
+ {
+ ///
+ /// Name of the Field
+ ///
+ public string FieldName { get; }
+
+ ///
+ /// Value of the Term
+ ///
+ public string FieldValue { get; }
+
+ ///
+ /// Constructor
+ ///
+ /// Name of the Field
+ /// Value of the Term
+ public FilterTerm(string fieldName, string fieldValue)
+ {
+ FieldName = fieldName;
+ FieldValue = fieldValue;
+ }
+
+ }
+}
diff --git a/src/Examine.Core/Search/IBooleanFilterOperation.cs b/src/Examine.Core/Search/IBooleanFilterOperation.cs
new file mode 100644
index 00000000..1f6af2b4
--- /dev/null
+++ b/src/Examine.Core/Search/IBooleanFilterOperation.cs
@@ -0,0 +1,49 @@
+using System;
+
+namespace Examine.Search
+{
+ public interface IBooleanFilterOperation
+ {
+ ///
+ /// Sets the next operation to be AND
+ ///
+ ///
+ IFilter AndFilter();
+
+ ///
+ /// Adds the nested filter
+ ///
+ ///
+ ///
+ ///
+ IBooleanFilterOperation AndFilter(Func inner, BooleanOperation defaultOp = BooleanOperation.And);
+
+ ///
+ /// Sets the next operation to be OR
+ ///
+ ///
+ IFilter OrFilter();
+
+ ///
+ /// Adds the nested filter
+ ///
+ ///
+ ///
+ ///
+ IBooleanFilterOperation OrFilter(Func inner, BooleanOperation defaultOp = BooleanOperation.And);
+
+ ///
+ /// Sets the next operation to be NOT
+ ///
+ ///
+ IFilter NotFilter();
+
+ ///
+ /// Adds the nested filter
+ ///
+ ///
+ ///
+ ///
+ IBooleanFilterOperation AndNotFilter(Func inner, BooleanOperation defaultOp = BooleanOperation.And);
+ }
+}
diff --git a/src/Examine.Core/Search/IFilter.cs b/src/Examine.Core/Search/IFilter.cs
new file mode 100644
index 00000000..44f5f0fa
--- /dev/null
+++ b/src/Examine.Core/Search/IFilter.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Generic;
+
+namespace Examine.Search
+{
+ public interface IFilter
+ {
+ ///
+ /// Term must match
+ ///
+ ///
+ ///
+ IBooleanFilterOperation TermFilter(FilterTerm term);
+
+ ///
+ /// Terms must match
+ ///
+ ///
+ ///
+ IBooleanFilterOperation TermsFilter(IEnumerable terms);
+
+ ///
+ /// Term must match as prefix
+ ///
+ ///
+ ///
+ IBooleanFilterOperation TermPrefixFilter(FilterTerm term);
+
+ ///
+ /// Document must have value for field
+ ///
+ ///
+ ///
+ IBooleanFilterOperation FieldValueExistsFilter(string field);
+
+ ///
+ /// Document must not have value for field
+ ///
+ ///
+ ///
+ IBooleanFilterOperation FieldValueNotExistsFilter(string field);
+
+ ///
+ /// Must match query
+ ///
+ ///
+ ///
+ ///
+ IBooleanFilterOperation QueryFilter(Func inner, BooleanOperation defaultOp = BooleanOperation.And);
+
+ ///
+ /// Matches items as defined by the IIndexFieldValueType used for the fields specified.
+ /// If a type is not defined for a field name, or the type does not implement IIndexRangeValueType for the types of min and max, nothing will be added
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ IBooleanFilterOperation IntRangeFilter(string field, int? min, int? max, bool minInclusive, bool maxInclusive);
+
+
+ ///
+ /// Matches items as defined by the IIndexFieldValueType used for the fields specified.
+ /// If a type is not defined for a field name, or the type does not implement IIndexRangeValueType for the types of min and max, nothing will be added
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ IBooleanFilterOperation LongRangeFilter(string field, long? min, long? max, bool minInclusive, bool maxInclusive);
+
+
+ ///
+ /// Matches items as defined by the IIndexFieldValueType used for the fields specified.
+ /// If a type is not defined for a field name, or the type does not implement IIndexRangeValueType for the types of min and max, nothing will be added
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ IBooleanFilterOperation FloatRangeFilter(string field, float? min, float? max, bool minInclusive, bool maxInclusive);
+
+
+ ///
+ /// Matches items as defined by the IIndexFieldValueType used for the fields specified.
+ /// If a type is not defined for a field name, or the type does not implement IIndexRangeValueType for the types of min and max, nothing will be added
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ IBooleanFilterOperation DoubleRangeFilter(string field, double? min, double? max, bool minInclusive, bool maxInclusive);
+ }
+}
diff --git a/src/Examine.Core/Search/INestedBooleanFilterOperation.cs b/src/Examine.Core/Search/INestedBooleanFilterOperation.cs
new file mode 100644
index 00000000..072bfe2e
--- /dev/null
+++ b/src/Examine.Core/Search/INestedBooleanFilterOperation.cs
@@ -0,0 +1,49 @@
+using System;
+
+namespace Examine.Search
+{
+ public interface INestedBooleanFilterOperation
+ {
+ ///
+ /// Sets the next operation to be AND
+ ///
+ ///
+ INestedFilter And();
+
+ ///
+ /// Adds the nested filter
+ ///
+ ///
+ ///
+ ///
+ INestedBooleanFilterOperation And(Func inner, BooleanOperation defaultOp = BooleanOperation.And);
+
+ ///
+ /// Sets the next operation to be OR
+ ///
+ ///
+ INestedFilter Or();
+
+ ///
+ /// Adds the nested filter
+ ///
+ ///
+ ///
+ ///
+ INestedBooleanFilterOperation Or(Func inner, BooleanOperation defaultOp = BooleanOperation.And);
+
+ ///
+ /// Sets the next operation to be NOT
+ ///
+ ///
+ INestedFilter Not();
+
+ ///
+ /// Adds the nested filter
+ ///
+ ///
+ ///
+ ///
+ INestedBooleanFilterOperation AndNot(Func inner, BooleanOperation defaultOp = BooleanOperation.And);
+ }
+}
diff --git a/src/Examine.Core/Search/INestedFilter.cs b/src/Examine.Core/Search/INestedFilter.cs
new file mode 100644
index 00000000..c157faf2
--- /dev/null
+++ b/src/Examine.Core/Search/INestedFilter.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+
+namespace Examine.Search
+{
+ public interface INestedFilter
+ {
+ ///
+ /// Term must match
+ ///
+ ///
+ ///
+ INestedBooleanFilterOperation NestedTermFilter(FilterTerm term);
+
+ ///
+ /// Terms must match
+ ///
+ ///
+ ///
+ INestedBooleanFilterOperation NestedTermsFilter(IEnumerable terms);
+
+ ///
+ /// Term must match as prefix
+ ///
+ ///
+ ///
+ INestedBooleanFilterOperation NestedTermPrefix(FilterTerm term);
+
+ ///
+ /// Document must have value for field
+ ///
+ ///
+ ///
+ INestedBooleanFilterOperation NestedFieldValueExists(string field);
+
+ ///
+ /// Document must not have value for field
+ ///
+ ///
+ ///
+ INestedBooleanFilterOperation NestedFieldValueNotExists(string field);
+
+ ///
+ /// Must match query
+ ///
+ ///
+ ///
+ ///
+ INestedBooleanFilterOperation NestedQueryFilter(Func inner, BooleanOperation defaultOp = BooleanOperation.And);
+ }
+}
diff --git a/src/Examine.Core/Search/IOrdering.cs b/src/Examine.Core/Search/IOrdering.cs
index 00acae92..806ae77f 100644
--- a/src/Examine.Core/Search/IOrdering.cs
+++ b/src/Examine.Core/Search/IOrdering.cs
@@ -40,6 +40,6 @@ public interface IOrdering : IQueryExecutor
/// Return all fields in the index
///
///
- IOrdering SelectAllFields();
+ IOrdering SelectAllFields();
}
}
diff --git a/src/Examine.Core/Search/IQuery.cs b/src/Examine.Core/Search/IQuery.cs
index 3a172e03..e9093176 100644
--- a/src/Examine.Core/Search/IQuery.cs
+++ b/src/Examine.Core/Search/IQuery.cs
@@ -134,5 +134,12 @@ public interface IQuery
///
///
IBooleanOperation RangeQuery(string[] fields, T? min, T? max, bool minInclusive = true, bool maxInclusive = true) where T : struct;
+
+ ///
+ /// Apply Filters
+ ///
+ ///
+ ///
+ IQuery WithFilter(Action filter);
}
}
diff --git a/src/Examine.Lucene/Indexing/FloatType.cs b/src/Examine.Lucene/Indexing/FloatType.cs
new file mode 100644
index 00000000..079f8e33
--- /dev/null
+++ b/src/Examine.Lucene/Indexing/FloatType.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using Examine.Lucene.Providers;
+using Examine.Lucene.Search;
+using Examine.Search;
+using Lucene.Net.Documents;
+using Lucene.Net.Facet;
+using Lucene.Net.Facet.SortedSet;
+using Lucene.Net.Search;
+using Microsoft.Extensions.Logging;
+using static Lucene.Net.Queries.Function.ValueSources.MultiFunction;
+
+namespace Examine.Lucene.Indexing
+{
+ ///
+ /// Represents a float/single
+ ///
+ public class FloatType : IndexFieldRangeValueType, IIndexFacetValueType
+ {
+ private readonly bool _isFacetable;
+#pragma warning disable IDE0032 // Use auto property
+ private readonly bool _taxonomyIndex;
+#pragma warning restore IDE0032 // Use auto property
+
+ ///
+ public FloatType(string fieldName, bool isFacetable, bool taxonomyIndex, ILoggerFactory logger, bool store)
+ : base(fieldName, logger, store)
+ {
+ _isFacetable = isFacetable;
+ _taxonomyIndex = taxonomyIndex;
+ }
+
+ ///
+ [Obsolete("To be removed in Examine V5")]
+#pragma warning disable RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads
+ public FloatType(string fieldName, ILoggerFactory logger, bool store = true)
+#pragma warning restore RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads
+ : base(fieldName, logger, store)
+ {
+ _isFacetable = false;
+ }
+
+ ///
+ /// Can be sorted by the normal field name
+ ///
+ public override string SortableFieldName => FieldName;
+
+ ///
+ public bool IsTaxonomyFaceted => _taxonomyIndex;
+
+ ///
+ public override void AddValue(Document doc, object? value)
+ {
+ // Support setting taxonomy path
+ if (_isFacetable && _taxonomyIndex && value is object[] objArr && objArr != null && objArr.Length == 2)
+ {
+ if (!TryConvert(objArr[0], out float parsedVal))
+ {
+ return;
+ }
+
+ if (!TryConvert(objArr[1], out string[]? parsedPathVal))
+ {
+ return;
+ }
+
+ doc.Add(new SingleField(FieldName, parsedVal, Store ? Field.Store.YES : Field.Store.NO));
+
+ doc.Add(new FacetField(FieldName, parsedPathVal));
+ doc.Add(new SingleDocValuesField(FieldName, parsedVal));
+ return;
+ }
+ base.AddValue(doc, value);
+ }
+
+ ///
+ protected override void AddSingleValue(Document doc, object value)
+ {
+ if (!TryConvert(value, out float parsedVal))
+ {
+ return;
+ }
+
+ doc.Add(new SingleField(FieldName, parsedVal, Store ? Field.Store.YES : Field.Store.NO));
+
+ if (_isFacetable && _taxonomyIndex)
+ {
+ doc.Add(new FacetField(FieldName, parsedVal.ToString()));
+ doc.Add(new SingleDocValuesField(FieldName, parsedVal));
+ }
+ else if (_isFacetable && !_taxonomyIndex)
+ {
+ doc.Add(new SortedSetDocValuesFacetField(FieldName, parsedVal.ToString()));
+ doc.Add(new SingleDocValuesField(FieldName, parsedVal));
+ }
+ }
+
+ ///
+ public override Query? GetQuery(string query) => !TryConvert(query, out float parsedVal) ? null : GetQuery(parsedVal, parsedVal);
+
+ ///
+ public override Query GetQuery(float? lower, float? upper, bool lowerInclusive = true, bool upperInclusive = true)
+ {
+ return NumericRangeQuery.NewSingleRange(FieldName,
+ lower ?? float.MinValue,
+ upper ?? float.MaxValue, lowerInclusive, upperInclusive);
+ }
+
+ ///
+ public virtual IEnumerable> ExtractFacets(IFacetExtractionContext facetExtractionContext, IFacetField field)
+ => field.ExtractFacets(facetExtractionContext);
+ }
+
+}
diff --git a/src/Examine.Lucene/Search/LuceneBooleanOperation.cs b/src/Examine.Lucene/Search/LuceneBooleanOperation.cs
index 18a7a044..1ec3ce83 100644
--- a/src/Examine.Lucene/Search/LuceneBooleanOperation.cs
+++ b/src/Examine.Lucene/Search/LuceneBooleanOperation.cs
@@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using Examine.Lucene.Providers;
using Examine.Search;
+using Lucene.Net.Facet;
using Lucene.Net.Search;
namespace Examine.Lucene.Search
diff --git a/src/Examine.Lucene/Search/LuceneBooleanOperationBase.cs b/src/Examine.Lucene/Search/LuceneBooleanOperationBase.cs
index e40ebe04..7cd70c17 100644
--- a/src/Examine.Lucene/Search/LuceneBooleanOperationBase.cs
+++ b/src/Examine.Lucene/Search/LuceneBooleanOperationBase.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Examine.Search;
+using Lucene.Net.Queries;
using Lucene.Net.Search;
namespace Examine.Lucene.Search
@@ -118,6 +119,42 @@ protected internal LuceneBooleanOperationBase Op(
return _search.LuceneQuery(_search.Queries.Pop(), outerOp);
}
+ ///
+ /// Used to add a operation
+ ///
+ /// Function that the base query will be passed into to create the outer Filter
+ ///
+ ///
+ ///
+ ///
+ internal LuceneBooleanOperationBase OpBaseFilter(
+ Func baseFilterBuilder,
+ Func inner,
+ BooleanOperation outerOp,
+ BooleanOperation? defaultInnerOp = null)
+ {
+ _search.Queries.Push(new BooleanQuery());
+
+ //change the default inner op if specified
+ var currentOp = _search.BooleanOperation;
+ if (defaultInnerOp != null)
+ {
+ _search.BooleanOperation = defaultInnerOp.Value;
+ }
+
+ //run the inner search
+ inner(_search);
+
+ //reset to original op if specified
+ if (defaultInnerOp != null)
+ {
+ _search.BooleanOperation = currentOp;
+ }
+ var baseBoolQuery = _search.Queries.Pop();
+ var baseFilter = baseFilterBuilder(baseBoolQuery);
+ return _search.LuceneFilter(baseFilter, outerOp);
+ }
+
///
public abstract ISearchResults Execute(QueryOptions? options = null);
@@ -138,5 +175,6 @@ protected internal LuceneBooleanOperationBase Op(
///
public abstract IQueryExecutor WithFacets(Action facets);
+
}
}
diff --git a/src/Examine.Lucene/Search/LuceneFilter.cs b/src/Examine.Lucene/Search/LuceneFilter.cs
new file mode 100644
index 00000000..9a7d336f
--- /dev/null
+++ b/src/Examine.Lucene/Search/LuceneFilter.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using Examine.Search;
+using Lucene.Net.Search;
+
+namespace Examine.Lucene.Search
+{
+ ///
+ /// Lucene Filter
+ ///
+ public class LuceneFilter: IFilter, INestedFilter
+ {
+ private readonly LuceneSearchFilteringOperation _search;
+
+ private readonly Occur _occurrence;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The filter.
+ /// The occurance.
+ public LuceneFilter(LuceneSearchFilteringOperation search, Occur occurrence)
+ {
+ _search = search;
+ _occurrence = occurrence;
+ }
+
+ ///
+ public IBooleanFilterOperation TermFilter(FilterTerm term) => _search.TermFilterInternal(term,_occurrence);
+
+ ///
+ public IBooleanFilterOperation TermsFilter(IEnumerable terms) => _search.TermsFilterInternal(terms, _occurrence);
+
+ ///
+ public IBooleanFilterOperation TermPrefixFilter(FilterTerm term) => _search.TermPrefixFilterInternal(term, _occurrence);
+
+ ///
+ public IBooleanFilterOperation FieldValueExistsFilter(string field) => _search.FieldValueExistsFilterInternal(field, _occurrence);
+
+ ///
+ public IBooleanFilterOperation FieldValueNotExistsFilter(string field) => _search.FieldValueNotExistsFilterInternal(field, _occurrence);
+
+ ///
+ public IBooleanFilterOperation QueryFilter(Func inner, BooleanOperation defaultOp = BooleanOperation.And) => _search.QueryFilterInternal(inner, defaultOp, _occurrence);
+
+ ///
+ public INestedBooleanFilterOperation NestedTermFilter(FilterTerm term) => _search.NestedTermFilterInternal(term, _occurrence);
+
+ ///
+ public INestedBooleanFilterOperation NestedTermsFilter(IEnumerable terms) => _search.NestedTermsFilterInternal(terms, _occurrence);
+
+ ///
+ public INestedBooleanFilterOperation NestedTermPrefix(FilterTerm term) => _search.NestedTermPrefixFilterInternal(term, _occurrence);
+
+ ///
+ public INestedBooleanFilterOperation NestedFieldValueExists(string field) => _search.NestedFieldValueExistsFilterInternal(field, _occurrence);
+
+ ///
+ public INestedBooleanFilterOperation NestedFieldValueNotExists(string field) => _search.NestedFieldValueNotExistsFilterInternal(field, _occurrence);
+
+ ///
+ public INestedBooleanFilterOperation NestedQueryFilter(Func inner, BooleanOperation defaultOp = BooleanOperation.And) => _search.NestedQueryFilterInternal(inner, defaultOp, _occurrence);
+
+ ///
+ public IBooleanFilterOperation IntRangeFilter(string field, int? min, int? max, bool minInclusive, bool maxInclusive) => _search.IntRangeFilterInternal(field, min, max, minInclusive, maxInclusive, _occurrence);
+
+ ///
+ public IBooleanFilterOperation LongRangeFilter(string field, long? min, long? max, bool minInclusive, bool maxInclusive) => _search.LongRangeFilterInternal(field, min, max, minInclusive, maxInclusive, _occurrence);
+
+ ///
+ public IBooleanFilterOperation FloatRangeFilter(string field, float? min, float? max, bool minInclusive, bool maxInclusive) => _search.FloatRangeFilterInternal(field, min, max, minInclusive, maxInclusive, _occurrence);
+
+ ///
+ public IBooleanFilterOperation DoubleRangeFilter(string field, double? min, double? max, bool minInclusive, bool maxInclusive) => _search.DoubleRangeFilterInternal(field, min, max, minInclusive, maxInclusive, _occurrence);
+ }
+}
diff --git a/src/Examine.Lucene/Search/LuceneFilteringBooleanOperation.cs b/src/Examine.Lucene/Search/LuceneFilteringBooleanOperation.cs
new file mode 100644
index 00000000..b1fc94a6
--- /dev/null
+++ b/src/Examine.Lucene/Search/LuceneFilteringBooleanOperation.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using Examine.Search;
+using Lucene.Net.Search;
+
+namespace Examine.Lucene.Search
+{
+ ///
+ /// Filter Boolean Operation
+ ///
+ public class LuceneFilteringBooleanOperation : LuceneFilteringBooleanOperationBase
+ {
+ private readonly LuceneSearchFilteringOperation _search;
+
+ ///
+ /// Constructor
+ ///
+ ///
+ public LuceneFilteringBooleanOperation(LuceneSearchFilteringOperation luceneSearch) : base(luceneSearch)
+ {
+ _search = luceneSearch;
+ }
+
+ #region IBooleanFilterOperation
+
+ ///
+ public override IFilter AndFilter() => new LuceneFilter(this._search, Occur.MUST);
+
+ ///
+ public override IFilter OrFilter() => new LuceneFilter(this._search, Occur.SHOULD);
+
+ ///
+ public override IFilter NotFilter() => new LuceneFilter(this._search, Occur.MUST_NOT);
+ #endregion
+
+ #region IFilter
+
+ ///
+ public override IBooleanFilterOperation TermFilter(FilterTerm term) => _search.TermFilter(term);
+
+ ///
+ public override IBooleanFilterOperation TermsFilter(IEnumerable terms) => _search.TermsFilter(terms);
+
+ ///
+ public override IBooleanFilterOperation TermPrefixFilter(FilterTerm term) => _search.TermPrefixFilter(term);
+
+ ///
+ public override IBooleanFilterOperation FieldValueExistsFilter(string field) => _search.FieldValueExistsFilter(field);
+
+ ///
+ public override IBooleanFilterOperation FieldValueNotExistsFilter(string field) => _search.FieldValueNotExistsFilter(field);
+
+ ///
+ public override IBooleanFilterOperation QueryFilter(Func inner, BooleanOperation defaultOp = BooleanOperation.And) => _search.QueryFilter(inner, defaultOp);
+
+ #endregion
+
+ #region INestedBooleanFilterOperation
+
+ ///
+ public override INestedFilter And() => new LuceneFilter(_search, Occur.MUST);
+
+ ///
+ public override INestedFilter Or() => new LuceneFilter(_search, Occur.SHOULD);
+
+ ///
+ public override INestedFilter Not() => new LuceneFilter(_search, Occur.MUST_NOT);
+
+ ///
+ public override IBooleanFilterOperation IntRangeFilter(string field, int? min, int? max, bool minInclusive, bool maxInclusive) => _search.IntRangeFilter(field, min, max, minInclusive, maxInclusive);
+
+ ///
+ public override IBooleanFilterOperation LongRangeFilter(string field, long? min, long? max, bool minInclusive, bool maxInclusive) => _search.LongRangeFilter(field, min, max, minInclusive, maxInclusive);
+
+ ///
+ public override IBooleanFilterOperation FloatRangeFilter(string field, float? min, float? max, bool minInclusive, bool maxInclusive) => _search.FloatRangeFilter(field, min, max, minInclusive, maxInclusive);
+
+ ///
+ public override IBooleanFilterOperation DoubleRangeFilter(string field, double? min, double? max, bool minInclusive, bool maxInclusive) => _search.DoubleRangeFilter(field, min, max, minInclusive, maxInclusive);
+
+ #endregion
+ }
+}
diff --git a/src/Examine.Lucene/Search/LuceneFilteringBooleanOperationBase.cs b/src/Examine.Lucene/Search/LuceneFilteringBooleanOperationBase.cs
new file mode 100644
index 00000000..156abfeb
--- /dev/null
+++ b/src/Examine.Lucene/Search/LuceneFilteringBooleanOperationBase.cs
@@ -0,0 +1,166 @@
+using System;
+using System.Collections.Generic;
+using Examine.Search;
+using Lucene.Net.Queries;
+using Lucene.Net.Search;
+
+namespace Examine.Lucene.Search
+{
+ ///
+ /// Boolean Lucene Filtering Operation Base
+ ///
+ public abstract class LuceneFilteringBooleanOperationBase : IFilter, IBooleanFilterOperation, INestedBooleanFilterOperation
+ {
+ private readonly LuceneSearchFilteringOperationBase _search;
+
+ ///
+ /// Constructor
+ ///
+ ///
+ public LuceneFilteringBooleanOperationBase(LuceneSearchFilteringOperationBase luceneSearch)
+ {
+ _search = luceneSearch;
+ }
+
+ ///
+ /// Used to add a operation
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal LuceneFilteringBooleanOperationBase Op(
+ Func inner,
+ BooleanOperation outerOp,
+ BooleanOperation? defaultInnerOp = null)
+ {
+ _search.Filters.Push(new BooleanFilter());
+
+ //change the default inner op if specified
+ var currentOp = _search.BooleanFilterOperation;
+ if (defaultInnerOp != null)
+ {
+ _search.BooleanFilterOperation = defaultInnerOp.Value;
+ }
+
+ //run the inner search
+ inner(_search);
+
+ //reset to original op if specified
+ if (defaultInnerOp != null)
+ {
+ _search.BooleanFilterOperation = currentOp;
+ }
+
+ return _search.LuceneFilter(_search.Filters.Pop(), outerOp);
+ }
+ ///
+ /// Used to add a operation
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal Filter GetNestedFilterOp(
+ Func inner,
+ BooleanOperation outerOp,
+ BooleanOperation? defaultInnerOp = null)
+ {
+ _search.Filters.Push(new BooleanFilter());
+
+ //change the default inner op if specified
+ var currentOp = _search.BooleanFilterOperation;
+ if (defaultInnerOp != null)
+ {
+ _search.BooleanFilterOperation = defaultInnerOp.Value;
+ }
+
+ //run the inner search
+ inner(_search);
+
+ //reset to original op if specified
+ if (defaultInnerOp != null)
+ {
+ _search.BooleanFilterOperation = currentOp;
+ }
+
+ return _search.Filters.Pop();
+ }
+
+ ///
+ public abstract IBooleanFilterOperation TermFilter(FilterTerm term);
+
+ ///
+ public abstract IBooleanFilterOperation TermsFilter(IEnumerable terms);
+
+ ///
+ public abstract IBooleanFilterOperation TermPrefixFilter(FilterTerm term);
+
+ ///
+ public abstract IBooleanFilterOperation FieldValueExistsFilter(string field);
+
+ ///
+ public abstract IBooleanFilterOperation FieldValueNotExistsFilter(string field);
+
+ ///
+ public abstract IBooleanFilterOperation QueryFilter(Func inner, BooleanOperation defaultOp = BooleanOperation.And);
+
+ #region IBooleanFilterOperation
+
+ ///
+ public abstract IFilter AndFilter();
+
+ ///
+ public IBooleanFilterOperation AndFilter(Func inner, BooleanOperation defaultOp = BooleanOperation.And)
+ => Op(inner, BooleanOperation.And, defaultOp);
+
+ ///
+ public abstract IFilter OrFilter();
+
+ ///
+ public IBooleanFilterOperation OrFilter(Func inner, BooleanOperation defaultOp = BooleanOperation.And)
+ => Op(inner, BooleanOperation.Or, defaultOp);
+
+ ///
+ public abstract IFilter NotFilter();
+
+ ///
+ public IBooleanFilterOperation AndNotFilter(Func inner, BooleanOperation defaultOp = BooleanOperation.And)
+ => Op(inner, BooleanOperation.Not, defaultOp);
+
+ ///
+ public abstract INestedFilter And();
+
+ ///
+ public INestedBooleanFilterOperation And(Func inner, BooleanOperation defaultOp = BooleanOperation.And)
+ => Op(inner, BooleanOperation.And, defaultOp);
+
+ ///
+ public abstract INestedFilter Or();
+
+ ///
+ public INestedBooleanFilterOperation Or(Func inner, BooleanOperation defaultOp = BooleanOperation.And)
+ => Op(inner, BooleanOperation.Or, defaultOp);
+
+ ///
+ public abstract INestedFilter Not();
+
+ ///
+ public INestedBooleanFilterOperation AndNot(Func inner, BooleanOperation defaultOp = BooleanOperation.And)
+ => Op(inner, BooleanOperation.Not, defaultOp);
+
+ ///
+ public abstract IBooleanFilterOperation IntRangeFilter(string field, int? min, int? max, bool minInclusive, bool maxInclusive);
+
+ ///
+ public abstract IBooleanFilterOperation LongRangeFilter(string field, long? min, long? max, bool minInclusive, bool maxInclusive);
+
+ ///
+ public abstract IBooleanFilterOperation FloatRangeFilter(string field, float? min, float? max, bool minInclusive, bool maxInclusive);
+
+ ///
+ public abstract IBooleanFilterOperation DoubleRangeFilter(string field, double? min, double? max, bool minInclusive, bool maxInclusive);
+
+ #endregion
+ }
+}
diff --git a/src/Examine.Lucene/Search/LuceneQuery.cs b/src/Examine.Lucene/Search/LuceneQuery.cs
index 42757bf7..29c745c5 100644
--- a/src/Examine.Lucene/Search/LuceneQuery.cs
+++ b/src/Examine.Lucene/Search/LuceneQuery.cs
@@ -1,9 +1,7 @@
using System;
-using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Examine.Search;
-using Lucene.Net.Facet.Range;
using Lucene.Net.Search;
namespace Examine.Lucene.Search
@@ -137,5 +135,9 @@ INestedBooleanOperation INestedQuery.ManagedQuery(string query, string[]? fields
///
INestedBooleanOperation INestedQuery.RangeQuery(string[] fields, T? min, T? max, bool minInclusive, bool maxInclusive)
=> _search.RangeQueryInternal(fields, min, max, minInclusive: minInclusive, maxInclusive: maxInclusive, _occurrence);
+
+ ///
+ public IQuery WithFilter(Action filter) => _search.WithFilter(filter);
+
}
}
diff --git a/src/Examine.Lucene/Search/LuceneSearchExecutor.cs b/src/Examine.Lucene/Search/LuceneSearchExecutor.cs
index 053a8e89..659337b7 100644
--- a/src/Examine.Lucene/Search/LuceneSearchExecutor.cs
+++ b/src/Examine.Lucene/Search/LuceneSearchExecutor.cs
@@ -27,10 +27,11 @@ public class LuceneSearchExecutor
private readonly ISet? _fieldsToLoad;
private readonly IEnumerable? _facetFields;
private readonly FacetsConfig? _facetsConfig;
+ private readonly Filter? _filter;
private int? _maxDoc;
internal LuceneSearchExecutor(QueryOptions? options, Query query, IEnumerable sortField, ISearchContext searchContext,
- ISet? fieldsToLoad, IEnumerable? facetFields, FacetsConfig? facetsConfig)
+ ISet? fieldsToLoad, IEnumerable? facetFields, FacetsConfig? facetsConfig, Filter? filter)
{
_options = options ?? QueryOptions.Default;
_luceneQueryOptions = _options as LuceneQueryOptions;
@@ -40,6 +41,7 @@ internal LuceneSearchExecutor(QueryOptions? options, Query query, IEnumerable 0)
{
topDocs = ((TopFieldCollector)topDocsCollector).GetTopDocs(_options.Skip, _options.Take);
diff --git a/src/Examine.Lucene/Search/LuceneSearchFiltering.cs b/src/Examine.Lucene/Search/LuceneSearchFiltering.cs
new file mode 100644
index 00000000..1cd01d88
--- /dev/null
+++ b/src/Examine.Lucene/Search/LuceneSearchFiltering.cs
@@ -0,0 +1,400 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Examine.Search;
+using Lucene.Net.Index;
+using Lucene.Net.Queries;
+using Lucene.Net.Search;
+
+namespace Examine.Lucene.Search
+{
+ ///
+ /// Lucene Search Filter Operation
+ ///
+ public class LuceneSearchFilteringOperation : LuceneSearchFilteringOperationBase
+ {
+ private readonly LuceneSearchQuery _luceneSearchQuery;
+
+ ///
+ /// Search Query
+ ///
+ public LuceneSearchQuery LuceneSearchQuery => _luceneSearchQuery;
+
+ ///
+ /// Constructor
+ ///
+ ///
+ public LuceneSearchFilteringOperation(LuceneSearchQuery luceneSearchQuery)
+ : base(luceneSearchQuery)
+ {
+ _luceneSearchQuery = luceneSearchQuery;
+ }
+
+ ///
+ /// Creates a new
+ ///
+ ///
+ protected override LuceneFilteringBooleanOperationBase CreateBooleanOp() => new LuceneFilteringBooleanOperation(this);
+
+ #region IFilter
+
+ ///
+ public override IBooleanFilterOperation TermFilter(FilterTerm term) => TermFilterInternal(term);
+
+ ///
+ internal IBooleanFilterOperation TermFilterInternal(FilterTerm term, Occur occurance = Occur.MUST)
+ {
+ if (term.FieldName is null)
+ {
+ throw new ArgumentNullException(nameof(term.FieldName));
+ }
+
+ var filterToAdd = new TermFilter(new Term(term.FieldName, term.FieldValue));
+ if (filterToAdd != null)
+ {
+ Filter.Add(filterToAdd, occurance);
+ }
+
+ return CreateBooleanOp();
+ }
+
+ ///
+ public override IBooleanFilterOperation TermsFilter(IEnumerable terms) => TermsFilterInternal(terms);
+
+ ///
+ internal IBooleanFilterOperation TermsFilterInternal(IEnumerable terms, Occur occurance = Occur.MUST)
+ {
+ if (terms is null)
+ {
+ throw new ArgumentNullException(nameof(terms));
+ }
+
+ if (!terms.Any() || terms.Any(x => string.IsNullOrWhiteSpace(x.FieldName)))
+ {
+ throw new ArgumentOutOfRangeException(nameof(terms));
+ }
+
+ var luceneTerms = terms.Select(x => new Term(x.FieldName, x.FieldValue)).ToArray();
+ var filterToAdd = new TermsFilter(luceneTerms);
+ if (filterToAdd != null)
+ {
+ Filter.Add(filterToAdd, occurance);
+ }
+
+ return CreateBooleanOp();
+ }
+
+ ///
+ public override IBooleanFilterOperation TermPrefixFilter(FilterTerm term) => TermPrefixFilterInternal(term);
+
+ ///
+ internal IBooleanFilterOperation TermPrefixFilterInternal(FilterTerm term, Occur occurance = Occur.MUST)
+ {
+ if (term.FieldName is null)
+ {
+ throw new ArgumentNullException(nameof(term.FieldName));
+ }
+
+ var filterToAdd = new PrefixFilter(new Term(term.FieldName, term.FieldValue));
+ if (filterToAdd != null)
+ {
+ Filter.Add(filterToAdd, occurance);
+ }
+
+ return CreateBooleanOp();
+ }
+
+ ///
+ public override IBooleanFilterOperation FieldValueExistsFilter(string field) => FieldValueExistsFilterInternal(field);
+
+ ///
+ internal IBooleanFilterOperation FieldValueExistsFilterInternal(string field, Occur occurance = Occur.MUST)
+ {
+ if (field is null)
+ {
+ throw new ArgumentNullException(nameof(field));
+ }
+
+ var filterToAdd = new FieldValueFilter(field);
+ if (filterToAdd != null)
+ {
+ Filter.Add(filterToAdd, occurance);
+ }
+
+ return CreateBooleanOp();
+ }
+
+ ///
+ public override IBooleanFilterOperation FieldValueNotExistsFilter(string field) => FieldValueNotExistsFilterInternal(field);
+
+ ///
+ internal IBooleanFilterOperation FieldValueNotExistsFilterInternal(string field, Occur occurance = Occur.MUST)
+ {
+ if (field is null)
+ {
+ throw new ArgumentNullException(nameof(field));
+ }
+
+ var filterToAdd = new FieldValueFilter(field);
+ if (filterToAdd != null)
+ {
+ Filter.Add(filterToAdd, occurance);
+ }
+
+ return CreateBooleanOp();
+ }
+
+ ///
+ public override IBooleanFilterOperation QueryFilter(Func inner, BooleanOperation defaultOp = BooleanOperation.And) => QueryFilterInternal(inner, defaultOp);
+
+ ///
+ internal IBooleanFilterOperation QueryFilterInternal(Func inner, BooleanOperation defaultOp, Occur occurance = Occur.MUST)
+ {
+ if (inner is null)
+ {
+ throw new ArgumentNullException(nameof(inner));
+ }
+
+ Func buildFilter = (baseQuery) =>
+ {
+ var queryWrapperFilter = new QueryWrapperFilter(baseQuery);
+
+ return queryWrapperFilter;
+ };
+
+ var bo = new LuceneBooleanOperation(_luceneSearchQuery);
+
+ var baseOp = bo.OpBaseFilter(buildFilter, inner, occurance.ToBooleanOperation(), defaultOp);
+
+ var op = CreateBooleanOp();
+ return op;
+ }
+
+ ///
+ public override IBooleanFilterOperation DoubleRangeFilter(string field, double? min, double? max, bool minInclusive, bool maxInclusive)
+ {
+ return DoubleRangeFilterInternal(field, min, max, minInclusive, maxInclusive);
+ }
+
+ internal IBooleanFilterOperation DoubleRangeFilterInternal(string field, double? min, double? max, bool minInclusive, bool maxInclusive, Occur occurance = Occur.MUST)
+ {
+ if (field is null)
+ {
+ throw new ArgumentNullException(nameof(field));
+ }
+
+ var filterToAdd = NumericRangeFilter.NewDoubleRange(field, min, max, minInclusive, maxInclusive);
+ if (filterToAdd != null)
+ {
+ Filter.Add(filterToAdd, occurance);
+ }
+
+ return CreateBooleanOp();
+ }
+
+ ///
+ public override IBooleanFilterOperation FloatRangeFilter(string field, float? min, float? max, bool minInclusive, bool maxInclusive)
+ {
+ return FloatRangeFilterInternal(field, min, max, minInclusive, maxInclusive);
+ }
+
+ internal IBooleanFilterOperation FloatRangeFilterInternal(string field, float? min, float? max, bool minInclusive, bool maxInclusive, Occur occurance = Occur.MUST)
+ {
+ if (field is null)
+ {
+ throw new ArgumentNullException(nameof(field));
+ }
+
+ var filterToAdd = NumericRangeFilter.NewSingleRange(field, min, max, minInclusive, maxInclusive);
+ if (filterToAdd != null)
+ {
+ Filter.Add(filterToAdd, occurance);
+ }
+
+ return CreateBooleanOp();
+ }
+
+ ///
+ public override IBooleanFilterOperation IntRangeFilter(string field, int? min, int? max, bool minInclusive, bool maxInclusive)
+ {
+ return IntRangeFilterInternal(field, min, max, minInclusive, maxInclusive);
+ }
+
+ internal IBooleanFilterOperation IntRangeFilterInternal(string field, int? min, int? max, bool minInclusive, bool maxInclusive, Occur occurance = Occur.MUST)
+ {
+ if (field is null)
+ {
+ throw new ArgumentNullException(nameof(field));
+ }
+
+ var filterToAdd = NumericRangeFilter.NewInt32Range(field, min, max, minInclusive, maxInclusive);
+ if (filterToAdd != null)
+ {
+ Filter.Add(filterToAdd, occurance);
+ }
+
+ return CreateBooleanOp();
+ }
+
+ ///
+ public override IBooleanFilterOperation LongRangeFilter(string field, long? min, long? max, bool minInclusive, bool maxInclusive)
+ {
+ return LongRangeFilterInternal(field, min, max, minInclusive, maxInclusive);
+ }
+
+ internal IBooleanFilterOperation LongRangeFilterInternal(string field, long? min, long? max, bool minInclusive, bool maxInclusive, Occur occurance = Occur.MUST)
+ {
+ if (field is null)
+ {
+ throw new ArgumentNullException(nameof(field));
+ }
+
+ var filterToAdd = NumericRangeFilter.NewInt64Range(field, min, max, minInclusive, maxInclusive);
+ if (filterToAdd != null)
+ {
+ Filter.Add(filterToAdd, occurance);
+ }
+
+ return CreateBooleanOp();
+ }
+ #endregion
+
+
+ #region INestedFilter
+
+ ///
+ protected override INestedBooleanFilterOperation NestedTermFilter(FilterTerm term) => NestedTermFilterInternal(term);
+
+ ///
+ protected override INestedBooleanFilterOperation NestedTermsFilter(IEnumerable terms) => NestedTermsFilterInternal(terms);
+
+ ///
+ protected override INestedBooleanFilterOperation NestedTermPrefixFilter(FilterTerm term) => NestedTermPrefixFilterInternal(term);
+
+ ///
+ protected override INestedBooleanFilterOperation NestedFieldValueExistsFilter(string field) => NestedFieldValueExistsFilterInternal(field);
+
+ ///
+ protected override INestedBooleanFilterOperation NestedFieldValueNotExistsFilter(string field) => NestedFieldValueNotExistsFilterInternal(field);
+
+ ///
+ protected override INestedBooleanFilterOperation NestedQueryFilter(Func inner, BooleanOperation defaultOp) => NestedQueryFilterInternal(inner, defaultOp);
+
+ ///
+ internal INestedBooleanFilterOperation NestedTermFilterInternal(FilterTerm term, Occur occurance = Occur.MUST)
+ {
+ if (term.FieldName is null)
+ {
+ throw new ArgumentNullException(nameof(term.FieldName));
+ }
+
+ var filterToAdd = new TermFilter(new Term(term.FieldName, term.FieldValue));
+ if (filterToAdd != null)
+ {
+ Filter.Add(filterToAdd, occurance);
+ }
+
+ return CreateBooleanOp();
+ }
+
+ ///
+ internal INestedBooleanFilterOperation NestedTermsFilterInternal(IEnumerable terms, Occur occurance = Occur.MUST)
+ {
+ if (terms is null)
+ {
+ throw new ArgumentNullException(nameof(terms));
+ }
+
+ if (!terms.Any() || terms.Any(x => string.IsNullOrWhiteSpace(x.FieldName)))
+ {
+ throw new ArgumentOutOfRangeException(nameof(terms));
+ }
+
+ var luceneTerms = terms.Select(x => new Term(x.FieldName, x.FieldValue)).ToArray();
+ var filterToAdd = new TermsFilter(luceneTerms);
+ if (filterToAdd != null)
+ {
+ Filter.Add(filterToAdd, occurance);
+ }
+
+ return CreateBooleanOp();
+ }
+
+ ///
+ internal INestedBooleanFilterOperation NestedTermPrefixFilterInternal(FilterTerm term, Occur occurance = Occur.MUST)
+ {
+ if (term.FieldName is null)
+ {
+ throw new ArgumentNullException(nameof(term.FieldName));
+ }
+
+ var filterToAdd = new PrefixFilter(new Term(term.FieldName, term.FieldValue));
+ if (filterToAdd != null)
+ {
+ Filter.Add(filterToAdd, occurance);
+ }
+
+ return CreateBooleanOp();
+ }
+
+ ///
+ internal INestedBooleanFilterOperation NestedFieldValueExistsFilterInternal(string field, Occur occurance = Occur.MUST)
+ {
+ if (field is null)
+ {
+ throw new ArgumentNullException(nameof(field));
+ }
+
+ var filterToAdd = new FieldValueFilter(field);
+ if (filterToAdd != null)
+ {
+ Filter.Add(filterToAdd, occurance);
+ }
+
+ return CreateBooleanOp();
+ }
+
+ ///
+ internal INestedBooleanFilterOperation NestedFieldValueNotExistsFilterInternal(string field, Occur occurance = Occur.MUST)
+ {
+ if (field is null)
+ {
+ throw new ArgumentNullException(nameof(field));
+ }
+
+ var filterToAdd = new FieldValueFilter(field);
+ if (filterToAdd != null)
+ {
+ Filter.Add(filterToAdd, occurance);
+ }
+
+ return CreateBooleanOp();
+ }
+
+ ///
+ internal INestedBooleanFilterOperation NestedQueryFilterInternal(Func inner, BooleanOperation defaultOp, Occur occurance = Occur.MUST)
+ {
+ if (inner is null)
+ {
+ throw new ArgumentNullException(nameof(inner));
+ }
+
+ Func buildFilter = (baseQuery) =>
+ {
+ var queryWrapperFilter = new QueryWrapperFilter(baseQuery);
+
+ return queryWrapperFilter;
+ };
+
+ var bo = new LuceneBooleanOperation(_luceneSearchQuery);
+
+ var baseOp = bo.OpBaseFilter(buildFilter, inner, occurance.ToBooleanOperation(), defaultOp);
+
+ var op = CreateBooleanOp();
+ return op;
+ }
+
+ #endregion
+
+ }
+}
diff --git a/src/Examine.Lucene/Search/LuceneSearchFilteringOperationBase.cs b/src/Examine.Lucene/Search/LuceneSearchFilteringOperationBase.cs
new file mode 100644
index 00000000..1a56a78d
--- /dev/null
+++ b/src/Examine.Lucene/Search/LuceneSearchFilteringOperationBase.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Collections.Generic;
+using Examine.Search;
+using Lucene.Net.Queries;
+using Lucene.Net.Search;
+
+namespace Examine.Lucene.Search
+{
+ ///
+ /// Filtering Operation
+ ///
+ public abstract class LuceneSearchFilteringOperationBase : IFilter, INestedFilter
+ {
+ internal Stack Filters => _luceneSearchQueryBase.Filters;
+
+ ///
+ /// The
+ ///
+ internal BooleanFilter Filter => _luceneSearchQueryBase.Filters.Peek();
+
+ private BooleanOperation _boolFilterOp;
+ private readonly LuceneSearchQueryBase _luceneSearchQueryBase;
+
+ ///
+ /// Specifies how clauses are to occur in matching documents
+ ///
+ protected Occur Occurrence { get; set; }
+
+ ///
+ /// Constructor
+ ///
+ ///
+ public LuceneSearchFilteringOperationBase(LuceneSearchQueryBase luceneSearchQueryBase)
+ {
+ _boolFilterOp = BooleanOperation.And;
+ _luceneSearchQueryBase = luceneSearchQueryBase;
+ }
+
+ ///
+ /// The type of boolean operation
+ ///
+ public BooleanOperation BooleanFilterOperation
+ {
+ get => _boolFilterOp;
+ set
+ {
+ _boolFilterOp = value;
+ Occurrence = _boolFilterOp.ToLuceneOccurrence();
+ }
+ }
+
+ ///
+ /// Adds a true Lucene Filter
+ ///
+ ///
+ ///
+ ///
+ public LuceneFilteringBooleanOperationBase LuceneFilter(Filter filter, BooleanOperation? op = null)
+ {
+ Filter.Add(filter, (op ?? BooleanFilterOperation).ToLuceneOccurrence());
+ return CreateBooleanOp();
+ }
+
+
+ ///
+ /// Creates a
+ ///
+ ///
+ protected abstract LuceneFilteringBooleanOperationBase CreateBooleanOp();
+
+ ///
+ public abstract IBooleanFilterOperation TermFilter(FilterTerm term);
+
+ ///
+ public abstract IBooleanFilterOperation TermsFilter(IEnumerable terms);
+
+ ///
+ public abstract IBooleanFilterOperation TermPrefixFilter(FilterTerm term);
+
+ ///
+ public abstract IBooleanFilterOperation FieldValueExistsFilter(string field);
+
+ ///
+ public abstract IBooleanFilterOperation FieldValueNotExistsFilter(string field);
+
+ ///
+ public abstract IBooleanFilterOperation QueryFilter(Func inner, BooleanOperation defaultOp = BooleanOperation.And);
+
+ ///
+ protected abstract INestedBooleanFilterOperation NestedTermFilter(FilterTerm term);
+
+ ///
+ protected abstract INestedBooleanFilterOperation NestedTermsFilter(IEnumerable terms);
+
+ ///
+ protected abstract INestedBooleanFilterOperation NestedTermPrefixFilter(FilterTerm term);
+
+ ///
+ protected abstract INestedBooleanFilterOperation NestedFieldValueExistsFilter(string field);
+
+ ///
+ protected abstract INestedBooleanFilterOperation NestedFieldValueNotExistsFilter(string field);
+
+ ///
+ protected abstract INestedBooleanFilterOperation NestedQueryFilter(Func inner, BooleanOperation defaultOp);
+
+ ///
+ INestedBooleanFilterOperation INestedFilter.NestedTermFilter(FilterTerm term) => NestedTermFilter(term);
+
+ ///
+ INestedBooleanFilterOperation INestedFilter.NestedTermsFilter(IEnumerable terms) => NestedTermsFilter(terms);
+
+ ///
+ INestedBooleanFilterOperation INestedFilter.NestedTermPrefix(FilterTerm term) => NestedTermPrefixFilter(term);
+
+ ///
+ INestedBooleanFilterOperation INestedFilter.NestedFieldValueExists(string field) => NestedFieldValueExistsFilter(field);
+
+ ///
+ INestedBooleanFilterOperation INestedFilter.NestedFieldValueNotExists(string field) => NestedFieldValueNotExistsFilter(field);
+
+ ///
+ INestedBooleanFilterOperation INestedFilter.NestedQueryFilter(Func inner, BooleanOperation defaultOp) => NestedQueryFilter(inner, defaultOp);
+
+ ///
+ public abstract IBooleanFilterOperation IntRangeFilter(string field, int? min, int? max, bool minInclusive, bool maxInclusive);
+
+ ///
+ public abstract IBooleanFilterOperation LongRangeFilter(string field, long? min, long? max, bool minInclusive, bool maxInclusive);
+
+ ///
+ public abstract IBooleanFilterOperation FloatRangeFilter(string field, float? min, float? max, bool minInclusive, bool maxInclusive);
+
+ ///
+ public abstract IBooleanFilterOperation DoubleRangeFilter(string field, double? min, double? max, bool minInclusive, bool maxInclusive);
+ }
+}
diff --git a/src/Examine.Lucene/Search/LuceneSearchQuery.cs b/src/Examine.Lucene/Search/LuceneSearchQuery.cs
index b1a787a3..3e036e10 100644
--- a/src/Examine.Lucene/Search/LuceneSearchQuery.cs
+++ b/src/Examine.Lucene/Search/LuceneSearchQuery.cs
@@ -6,7 +6,10 @@
using Examine.Search;
using Lucene.Net.Analysis;
using Lucene.Net.Facet;
+using Lucene.Net.Index;
+using Lucene.Net.Queries;
using Lucene.Net.Search;
+using static Lucene.Net.Util.OfflineSorter;
namespace Examine.Lucene.Search
{
@@ -258,7 +261,14 @@ private ISearchResults Search(QueryOptions? options)
}
}
- var executor = new LuceneSearchExecutor(options, query, SortFields, _searchContext, _fieldsToLoad, _facetFields, _facetsConfig);
+ // capture local
+ Filter? filter = Filter;
+ if (filter is BooleanFilter boolFilter && boolFilter.Clauses.Count == 0)
+ {
+ filter = null;
+ }
+
+ var executor = new LuceneSearchExecutor(options, query, SortFields, _searchContext, _fieldsToLoad, _facetFields,_facetsConfig, filter);
var pagesResults = executor.Execute();
@@ -443,5 +453,15 @@ private bool GetFacetFieldIsHierarchical(string field)
}
return false;
}
+
+ ///
+ public override IQuery WithFilter(Action filter)
+ {
+ var lfilter = new LuceneSearchFilteringOperation(this);
+ filter.Invoke(lfilter);
+ var op = CreateOp();
+ var queryOp = op.And();
+ return queryOp;
+ }
}
}
diff --git a/src/Examine.Lucene/Search/LuceneSearchQueryBase.cs b/src/Examine.Lucene/Search/LuceneSearchQueryBase.cs
index e8960684..b43eeb1d 100644
--- a/src/Examine.Lucene/Search/LuceneSearchQueryBase.cs
+++ b/src/Examine.Lucene/Search/LuceneSearchQueryBase.cs
@@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Examine.Search;
-using Lucene.Net.Facet.Range;
using Lucene.Net.Index;
+using Lucene.Net.Queries;
using Lucene.Net.QueryParsers.Classic;
using Lucene.Net.Search;
@@ -34,12 +33,22 @@ public abstract class LuceneSearchQueryBase : IQuery, INestedQuery
///
public IList SortFields { get; } = new List();
+ internal Stack Filters { get; } = new Stack();
+
+ ///
+ /// The
+ ///
+ public BooleanFilter Filter => Filters.Peek();
+
///
/// Specifies how clauses are to occur in matching documents
///
protected Occur Occurrence { get; set; }
private BooleanOperation _boolOp;
+ private BooleanOperation _boolFilterOp;
+
+
///
protected LuceneSearchQueryBase(CustomMultiFieldQueryParser queryParser,
string? category, LuceneSearchOptions searchOptions, BooleanOperation occurance)
@@ -47,6 +56,7 @@ protected LuceneSearchQueryBase(CustomMultiFieldQueryParser queryParser,
Category = category;
SearchOptions = searchOptions;
Queries.Push(new BooleanQuery());
+ Filters.Push(new BooleanFilter());
BooleanOperation = occurance;
_queryParser = queryParser;
}
@@ -70,6 +80,19 @@ public BooleanOperation BooleanOperation
}
}
+ ///
+ /// The type of boolean operation
+ ///
+ public BooleanOperation BooleanFilterOperation
+ {
+ get => _boolFilterOp;
+ set
+ {
+ _boolFilterOp = value;
+ Occurrence = _boolFilterOp.ToLuceneOccurrence();
+ }
+ }
+
///
/// The category of the query
///
@@ -119,6 +142,19 @@ public LuceneBooleanOperationBase LuceneQuery(Query query, BooleanOperation? op
return CreateOp();
}
+ ///
+ /// Adds a true Lucene Filter
+ ///
+ ///
+ ///
+ ///
+ public LuceneBooleanOperationBase LuceneFilter(Filter filter, BooleanOperation? op = null)
+ {
+ Filter.Add(filter, (op ?? BooleanOperation).ToLuceneOccurrence());
+ return CreateOp();
+ }
+
+
///
public IBooleanOperation Id(string id) => IdInternal(id, Occurrence);
@@ -632,5 +668,12 @@ private BooleanQuery GetMultiFieldQuery(
/// A that represents this instance.
///
public override string ToString() => $"{{ Category: {Category}, LuceneQuery: {Query} }}";
+
+ ///
+ /// Apply a filter
+ ///
+ ///
+ ///
+ public abstract IQuery WithFilter(Action filter);
}
}
diff --git a/src/Examine.Lucene/ValueTypeFactoryCollection.cs b/src/Examine.Lucene/ValueTypeFactoryCollection.cs
index dc8f955b..9952f890 100644
--- a/src/Examine.Lucene/ValueTypeFactoryCollection.cs
+++ b/src/Examine.Lucene/ValueTypeFactoryCollection.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using System.Runtime.InteropServices;
using Examine.Lucene.Analyzers;
using Examine.Lucene.Indexing;
using Lucene.Net.Analysis;
@@ -75,7 +76,8 @@ private static IReadOnlyDictionary> G
{
{"number", name => new Int32Type(name, loggerFactory)},
{FieldDefinitionTypes.Integer, name => new Int32Type(name, loggerFactory)},
- {FieldDefinitionTypes.Float, name => new SingleType(name, loggerFactory)},
+ {FieldDefinitionTypes.Float, name => new FloatType(name, loggerFactory)},
+ {FieldDefinitionTypes.Single, name => new SingleType(name, loggerFactory)},
{FieldDefinitionTypes.Double, name => new DoubleType(name, loggerFactory)},
{FieldDefinitionTypes.Long, name => new Int64Type(name, loggerFactory)},
{"date", name => new DateTimeType(name, loggerFactory, DateResolution.MILLISECOND)},
@@ -91,7 +93,7 @@ private static IReadOnlyDictionary> G
{FieldDefinitionTypes.InvariantCultureIgnoreCase, name => new GenericAnalyzerFieldValueType(name, loggerFactory, new CultureInvariantWhitespaceAnalyzer())},
{FieldDefinitionTypes.EmailAddress, name => new GenericAnalyzerFieldValueType(name, loggerFactory, new EmailAddressAnalyzer())},
{FieldDefinitionTypes.FacetInteger, name => new Int32Type(name, true,false,loggerFactory, true)},
- {FieldDefinitionTypes.FacetFloat, name => new SingleType(name, true, false, loggerFactory, true)},
+ {FieldDefinitionTypes.FacetFloat, name => new FloatType(name, true, false, loggerFactory, true)},
{FieldDefinitionTypes.FacetDouble, name => new DoubleType(name,true, false, loggerFactory, true)},
{FieldDefinitionTypes.FacetLong, name => new Int64Type(name, true, false, loggerFactory, true)},
{FieldDefinitionTypes.FacetDateTime, name => new DateTimeType(name, true, true, false, loggerFactory, DateResolution.MILLISECOND)},
@@ -103,7 +105,7 @@ private static IReadOnlyDictionary> G
{FieldDefinitionTypes.FacetFullText, name => new FullTextType(name, loggerFactory, true, false, false, defaultAnalyzer ?? new CultureInvariantStandardAnalyzer())},
{FieldDefinitionTypes.FacetFullTextSortable, name => new FullTextType(name, loggerFactory, true, false,true, defaultAnalyzer ?? new CultureInvariantStandardAnalyzer())},
{FieldDefinitionTypes.FacetTaxonomyInteger, name => new Int32Type(name,true,true, loggerFactory, true)},
- {FieldDefinitionTypes.FacetTaxonomyFloat, name => new SingleType(name,isFacetable: true, taxonomyIndex: true, loggerFactory, true)},
+ {FieldDefinitionTypes.FacetTaxonomyFloat, name => new FloatType(name,isFacetable: true, taxonomyIndex: true, loggerFactory, true)},
{FieldDefinitionTypes.FacetTaxonomyDouble, name => new DoubleType(name, true, true, loggerFactory, true)},
{FieldDefinitionTypes.FacetTaxonomyLong, name => new Int64Type(name, isFacetable: true, taxonomyIndex: true, loggerFactory, true)},
{FieldDefinitionTypes.FacetTaxonomyDateTime, name => new DateTimeType(name,true, true, taxonomyIndex : true, loggerFactory, DateResolution.MILLISECOND)},
diff --git a/src/Examine.Test/Examine.Lucene/Search/FluentApiTests.cs b/src/Examine.Test/Examine.Lucene/Search/FluentApiTests.cs
index a7e8746d..230eda18 100644
--- a/src/Examine.Test/Examine.Lucene/Search/FluentApiTests.cs
+++ b/src/Examine.Test/Examine.Lucene/Search/FluentApiTests.cs
@@ -5,13 +5,16 @@
using Examine.Lucene.Providers;
using Examine.Lucene.Search;
using Examine.Search;
+using Lucene.Net.Analysis;
using Lucene.Net.Analysis.En;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Facet;
+using Lucene.Net.Queries;
using Lucene.Net.QueryParsers.Classic;
using Lucene.Net.Search;
+using Lucene.Net.Store;
using NUnit.Framework;
-
+using LuceneTerm = Lucene.Net.Index.Term;
namespace Examine.Test.Examine.Lucene.Search
@@ -4900,11 +4903,12 @@ public void SearchAfter_NonSorted_Results_Returns_Different_Results()
}
#if NET6_0_OR_GREATER
- [TestCase(FacetTestType.TaxonomyFacets)] [TestCase(FacetTestType.SortedSetFacets)]
+ [TestCase(FacetTestType.TaxonomyFacets)]
+ [TestCase(FacetTestType.SortedSetFacets)]
[TestCase(FacetTestType.NoFacets)]
public void Range_DateOnly(FacetTestType withFacets)
{
- FieldDefinitionCollection fieldDefinitionCollection = null;
+ FieldDefinitionCollection fieldDefinitionCollection = null;
switch (withFacets)
{
case FacetTestType.TaxonomyFacets:
@@ -5190,5 +5194,561 @@ public void GivenTaxonomyIndexSearchAfterTake_Returns_ExpectedTotals_Facet(int f
}
}
+
+ [TestCase(FacetTestType.TaxonomyFacets)]
+ [TestCase(FacetTestType.SortedSetFacets)]
+ [TestCase(FacetTestType.NoFacets)]
+ public void TermFilter(FacetTestType withFacets)
+ {
+ Action actAssertAction
+ = (fieldDefinitionCollection, indexAnalyzer, indexDirectory, taxonomyDirectory, testIndex, searcher)
+ =>
+ {
+ var criteria = searcher.CreateQuery("content")
+ .WithFilter(
+ filter =>
+ {
+ filter.TermFilter(new FilterTerm("nodeTypeAlias", "CWS_Home"));
+ });
+ var boolOp = criteria.All();
+
+ if (HasFacets(withFacets))
+ {
+ var results = boolOp.WithFacets(facets => facets.FacetString("nodeName")).Execute();
+
+ var facetResults = results.GetFacet("nodeName");
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ Assert.AreEqual(2, facetResults.Count());
+ }
+ else
+ {
+ var results = boolOp.Execute();
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ }
+ };
+
+ RunFilterTest(withFacets, actAssertAction);
+ }
+
+
+ [TestCase(FacetTestType.TaxonomyFacets)]
+ [TestCase(FacetTestType.SortedSetFacets)]
+ [TestCase(FacetTestType.NoFacets)]
+ public void TermPrefixFilter(FacetTestType withFacets)
+ {
+ Action actAssertAction
+ = (fieldDefinitionCollection, indexAnalyzer, indexDirectory, taxonomyDirectory, testIndex, searcher)
+ =>
+ {
+
+ var criteria = searcher.CreateQuery("content")
+ .WithFilter(
+ filter =>
+ {
+ filter.TermPrefixFilter(new FilterTerm("nodeTypeAlias", "CWS_H"));
+ });
+ var boolOp = criteria.All();//.Field("nodeTypeAlias", "CWS_Home".Escape());
+
+ if (HasFacets(withFacets))
+ {
+ var results = boolOp.WithFacets(facets => facets.FacetString("nodeName")).Execute();
+
+ var facetResults = results.GetFacet("nodeName");
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ Assert.AreEqual(2, facetResults.Count());
+ }
+ else
+ {
+ var results = boolOp.Execute();
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ }
+ };
+
+ RunFilterTest(withFacets, actAssertAction);
+ }
+
+
+ [TestCase(FacetTestType.TaxonomyFacets)]
+ [TestCase(FacetTestType.SortedSetFacets)]
+ [TestCase(FacetTestType.NoFacets)]
+ public void TermsFilter(FacetTestType withFacets)
+ {
+ Action actAssertAction
+ = (fieldDefinitionCollection, indexAnalyzer, indexDirectory, taxonomyDirectory, testIndex, searcher)
+ =>
+ {
+
+ var criteria = searcher.CreateQuery("content")
+ .WithFilter(
+ filter =>
+ {
+ filter.TermsFilter(new[] {
+ new FilterTerm("nodeTypeAlias", "CWS_Home"),
+ new FilterTerm("nodeName", "my name 2")
+ });
+ });
+ var boolOp = criteria.All();
+
+ if (HasFacets(withFacets))
+ {
+ var results = boolOp.WithFacets(facets => facets.FacetString("nodeName")).Execute();
+
+ var facetResults = results.GetFacet("nodeName");
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ Assert.AreEqual(2, facetResults.Count());
+ }
+ else
+ {
+ var results = boolOp.Execute();
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ }
+ };
+
+ RunFilterTest(withFacets, actAssertAction);
+ }
+
+ [TestCase(FacetTestType.TaxonomyFacets)]
+ [TestCase(FacetTestType.SortedSetFacets)]
+ [TestCase(FacetTestType.NoFacets)]
+ public void TermAndTermPrefixFilter(FacetTestType withFacets)
+ {
+ Action actAssertAction
+ = (fieldDefinitionCollection, indexAnalyzer, indexDirectory, taxonomyDirectory, testIndex, searcher)
+ =>
+ {
+ var criteria = searcher.CreateQuery("content")
+ .WithFilter(
+ filter =>
+ {
+ filter.TermFilter(new FilterTerm("nodeTypeAlias", "CWS_Home"))
+ .AndFilter()
+ .TermPrefixFilter(new FilterTerm("nodeTypeAlias", "CWS_H"));
+ });
+ var boolOp = criteria.All();
+
+ if (HasFacets(withFacets))
+ {
+ var results = boolOp.WithFacets(facets => facets.FacetString("nodeName")).Execute();
+
+ var facetResults = results.GetFacet("nodeName");
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ Assert.AreEqual(2, facetResults.Count());
+ }
+ else
+ {
+ var results = boolOp.Execute();
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ }
+ };
+
+ RunFilterTest(withFacets, actAssertAction);
+ }
+
+ [TestCase(FacetTestType.TaxonomyFacets)]
+ [TestCase(FacetTestType.SortedSetFacets)]
+ [TestCase(FacetTestType.NoFacets)]
+ public void TermAndNotTermPrefixFilter(FacetTestType withFacets)
+ {
+ Action actAssertAction
+ = (fieldDefinitionCollection, indexAnalyzer, indexDirectory, taxonomyDirectory, testIndex, searcher)
+ =>
+ {
+ var criteria = searcher.CreateQuery("content")
+ .WithFilter(
+ filter =>
+ {
+ filter.TermPrefixFilter(new FilterTerm("nodeTypeAlias", "CWS_"))
+ .AndNotFilter(
+ inner => inner.NestedTermFilter(new FilterTerm("nodeTypeAlias", "CWS_Home")));
+ });
+ var boolOp = criteria.All();
+
+ if (HasFacets(withFacets))
+ {
+ var results = boolOp.WithFacets(facets => facets.FacetString("nodeName")).Execute();
+
+ var facetResults = results.GetFacet("nodeName");
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ Assert.AreEqual(2, facetResults.Count());
+
+ Assert.IsTrue(results.All(x => x.Values["nodeTypeAlias"].StartsWith("CWS_")));
+ Assert.IsFalse(results.Any(x => x.Values["nodeTypeAlias"] == "CWS_Home"));
+
+ }
+ else
+ {
+ var results = boolOp.Execute();
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ Assert.IsTrue(results.All(x => x.Values["nodeTypeAlias"].StartsWith("CWS_")));
+ Assert.IsFalse(results.Any(x => x.Values["nodeTypeAlias"] == "CWS_Home"));
+ }
+ };
+
+ RunFilterTest(withFacets, actAssertAction);
+ }
+
+ [TestCase(FacetTestType.TaxonomyFacets)]
+ [TestCase(FacetTestType.SortedSetFacets)]
+ [TestCase(FacetTestType.NoFacets)]
+ public void QueryFilter(FacetTestType withFacets)
+ {
+ Action actAssertAction
+ = (fieldDefinitionCollection, indexAnalyzer, indexDirectory, taxonomyDirectory, testIndex, searcher)
+ =>
+ {
+
+ var criteria = searcher.CreateQuery("content")
+ .WithFilter(
+ filter =>
+ {
+ filter.QueryFilter(
+ query =>
+ query.Field("nodeTypeAlias", "CWS_Home"));
+ });
+ var boolOp = criteria.All();
+
+ if (HasFacets(withFacets))
+ {
+ var results = boolOp.WithFacets(facets => facets.FacetString("nodeName")).Execute();
+
+ var facetResults = results.GetFacet("nodeName");
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ Assert.AreEqual(2, facetResults.Count());
+ }
+ else
+ {
+ var results = boolOp.Execute();
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ }
+ };
+
+ RunFilterTest(withFacets, actAssertAction);
+ }
+
+ [TestCase(FacetTestType.TaxonomyFacets)]
+ [TestCase(FacetTestType.SortedSetFacets)]
+ [TestCase(FacetTestType.NoFacets)]
+ public void NestedQueryFilter(FacetTestType withFacets)
+ {
+ Action actAssertAction
+ = (fieldDefinitionCollection, indexAnalyzer, indexDirectory, taxonomyDirectory, testIndex, searcher)
+ =>
+ {
+
+ var criteria = searcher.CreateQuery("content")
+ .WithFilter(
+ filter =>
+ {
+ filter.TermFilter(new FilterTerm("nodeTypeAlias", "CWS_Home"))
+ .AndFilter(
+ innerFilter => innerFilter.NestedQueryFilter(
+ query => query.Field("nodeTypeAlias", "CWS_Home"))
+ );
+
+ });
+ var boolOp = criteria.All();
+
+ if (HasFacets(withFacets))
+ {
+ var results = boolOp.WithFacets(facets => facets.FacetString("nodeName")).Execute();
+
+ var facetResults = results.GetFacet("nodeName");
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ Assert.AreEqual(2, facetResults.Count());
+ }
+ else
+ {
+ var results = boolOp.Execute();
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ }
+ };
+
+ RunFilterTest(withFacets, actAssertAction);
+ }
+
+ [TestCase(FacetTestType.TaxonomyFacets)]
+ [TestCase(FacetTestType.SortedSetFacets)]
+ [TestCase(FacetTestType.NoFacets)]
+ public void IntRangeFilter(FacetTestType withFacets)
+ {
+ Action actAssertAction
+ = (fieldDefinitionCollection, indexAnalyzer, indexDirectory, taxonomyDirectory, testIndex, searcher)
+ =>
+ {
+ var criteria = searcher.CreateQuery("content")
+ .WithFilter(
+ filter =>
+ {
+ filter.IntRangeFilter("intNumber", 2, 3, true, true);
+ });
+ var boolOp = criteria.All();
+
+ if (HasFacets(withFacets))
+ {
+ var results = boolOp.WithFacets(facets => facets.FacetString("nodeName")).Execute();
+
+ var facetResults = results.GetFacet("nodeName");
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ Assert.AreEqual(2, facetResults.Count());
+ }
+ else
+ {
+ var results = boolOp.Execute();
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ }
+ };
+
+ RunFilterTest(withFacets, actAssertAction);
+ }
+
+
+ [TestCase(FacetTestType.TaxonomyFacets)]
+ [TestCase(FacetTestType.SortedSetFacets)]
+ [TestCase(FacetTestType.NoFacets)]
+ public void LongRangeFilter(FacetTestType withFacets)
+ {
+ Action actAssertAction
+ = (fieldDefinitionCollection, indexAnalyzer, indexDirectory, taxonomyDirectory, testIndex, searcher)
+ =>
+ {
+ var criteria = searcher.CreateQuery("content")
+ .WithFilter(
+ filter =>
+ {
+ filter.LongRangeFilter("longNumber", 2, 3, true, true);
+ });
+ var boolOp = criteria.All();
+
+ if (HasFacets(withFacets))
+ {
+ var results = boolOp.WithFacets(facets => facets.FacetString("nodeName")).Execute();
+
+ var facetResults = results.GetFacet("nodeName");
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ Assert.AreEqual(2, facetResults.Count());
+ }
+ else
+ {
+ var results = boolOp.Execute();
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ }
+ };
+
+ RunFilterTest(withFacets, actAssertAction);
+ }
+
+ [TestCase(FacetTestType.TaxonomyFacets)]
+ [TestCase(FacetTestType.SortedSetFacets)]
+ [TestCase(FacetTestType.NoFacets)]
+ public void FloatRangeFilter(FacetTestType withFacets)
+ {
+ Action actAssertAction
+ = (fieldDefinitionCollection, indexAnalyzer, indexDirectory, taxonomyDirectory, testIndex, searcher)
+ =>
+ {
+ var criteria = searcher.CreateQuery("content")
+ .WithFilter(
+ filter =>
+ {
+ filter.FloatRangeFilter("floatNumber", 2.0f, 3.0f, true, true);
+ });
+ var boolOp = criteria.All();
+
+ if (HasFacets(withFacets))
+ {
+ var results = boolOp.WithFacets(facets => facets.FacetString("nodeName")).Execute();
+
+ var facetResults = results.GetFacet("nodeName");
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ Assert.AreEqual(2, facetResults.Count());
+ }
+ else
+ {
+ var results = boolOp.Execute();
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ }
+ };
+
+ RunFilterTest(withFacets, actAssertAction);
+ }
+
+ [TestCase(FacetTestType.TaxonomyFacets)]
+ [TestCase(FacetTestType.SortedSetFacets)]
+ [TestCase(FacetTestType.NoFacets)]
+ public void DoubleRangeFilter(FacetTestType withFacets)
+ {
+ Action actAssertAction
+ = (fieldDefinitionCollection, indexAnalyzer, indexDirectory, taxonomyDirectory, testIndex, searcher)
+ =>
+ {
+ var criteria = searcher.CreateQuery("content")
+ .WithFilter(
+ filter =>
+ {
+ filter.DoubleRangeFilter("doubleNumber", 2.0, 3.0, true, true);
+ });
+ var boolOp = criteria.All();
+
+ if (HasFacets(withFacets))
+ {
+ var results = boolOp.WithFacets(facets => facets.FacetString("nodeName")).Execute();
+
+ var facetResults = results.GetFacet("nodeName");
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ Assert.AreEqual(2, facetResults.Count());
+ }
+ else
+ {
+ var results = boolOp.Execute();
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ }
+ };
+
+ RunFilterTest(withFacets, actAssertAction);
+ }
+
+ [TestCase(FacetTestType.TaxonomyFacets)]
+ [TestCase(FacetTestType.SortedSetFacets)]
+ [TestCase(FacetTestType.NoFacets)]
+ public void Custom_Lucene_Filter(FacetTestType withFacets)
+ {
+
+ Action actAssertAction
+ = (fieldDefinitionCollection, indexAnalyzer, indexDirectory, taxonomyDirectory, testIndex, searcher)
+ =>
+ {
+ var criteria = (LuceneSearchQuery)searcher.CreateQuery("content");
+
+ criteria.LuceneFilter(new TermFilter(new LuceneTerm("nodeTypeAlias", "CWS_Home")));
+ var boolOp = criteria.All();
+
+ if (HasFacets(withFacets))
+ {
+ var results = boolOp.WithFacets(facets => facets.FacetString("nodeName")).Execute();
+
+ var facetResults = results.GetFacet("nodeName");
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ Assert.AreEqual(2, facetResults.Count());
+ }
+ else
+ {
+ var results = boolOp.Execute();
+
+ Assert.AreEqual(2, results.TotalItemCount);
+ }
+ };
+ RunFilterTest(withFacets, actAssertAction);
+ }
+
+ private void RunFilterTest(FacetTestType withFacets, Action actAssertAction)
+ {
+ FieldDefinitionCollection fieldDefinitionCollection = null;
+ switch (withFacets)
+ {
+ case FacetTestType.TaxonomyFacets:
+
+ fieldDefinitionCollection = new FieldDefinitionCollection(new FieldDefinition("nodeTypeAlias", "raw"),
+ new FieldDefinition("longNumber", FieldDefinitionTypes.Long),
+ new FieldDefinition("intNumber", FieldDefinitionTypes.Integer),
+ new FieldDefinition("floatNumber", FieldDefinitionTypes.Float),
+ new FieldDefinition("doubleNumber", FieldDefinitionTypes.Double),
+ new FieldDefinition("nodeName", FieldDefinitionTypes.FacetTaxonomyFullText));
+ break;
+ case FacetTestType.SortedSetFacets:
+ fieldDefinitionCollection = new FieldDefinitionCollection(new FieldDefinition("nodeTypeAlias", "raw"),
+ new FieldDefinition("longNumber", FieldDefinitionTypes.Long),
+ new FieldDefinition("intNumber", FieldDefinitionTypes.Integer),
+ new FieldDefinition("floatNumber", FieldDefinitionTypes.Float),
+ new FieldDefinition("doubleNumber", FieldDefinitionTypes.Double),
+ new FieldDefinition("nodeName", FieldDefinitionTypes.FacetFullText));
+ break;
+ default:
+ fieldDefinitionCollection = new FieldDefinitionCollection(new FieldDefinition("nodeTypeAlias", "raw"),
+ new FieldDefinition("intNumber", FieldDefinitionTypes.Integer),
+ new FieldDefinition("floatNumber", FieldDefinitionTypes.Float),
+ new FieldDefinition("doubleNumber", FieldDefinitionTypes.Double),
+ new FieldDefinition("longNumber", FieldDefinitionTypes.Long));
+ break;
+ }
+
+ var analyzer = new StandardAnalyzer(LuceneInfo.CurrentVersion);
+ using (var luceneDir = new RandomIdRAMDirectory())
+ using (var luceneTaxonomyDir = new RandomIdRAMDirectory())
+ using (var indexer = GetTaxonomyTestIndex(
+ luceneDir,
+ luceneTaxonomyDir,
+ analyzer,
+ fieldDefinitionCollection))
+ {
+ indexer.IndexItems(new[] {
+ new ValueSet(1.ToString(), "content",
+ new Dictionary
+ {
+ {"nodeName", "my name 1"},
+ {"nodeTypeAlias", "CWS_Home"},
+ { "longNumber", 1 },
+ { "intNumber", 1 },
+ { "floatNumber", 1.0f },
+ { "doubleNumber", 1.0 }
+ }),
+ new ValueSet(2.ToString(), "content",
+ new Dictionary
+ {
+ {"nodeName", "my name 2"},
+ {"nodeTypeAlias", "CWS_Home"},
+ { "longNumber",2 },
+ { "intNumber", 2 },
+ { "floatNumber", 2.0f },
+ { "doubleNumber", 2.0 }
+ }),
+ new ValueSet(3.ToString(), "content",
+ new Dictionary
+ {
+ {"nodeName", "my name 3"},
+ {"nodeTypeAlias", "CWS_Page"},
+ { "longNumber", 3 },
+ { "intNumber", 3 },
+ { "floatNumber", 3.0f },
+ { "doubleNumber", 3.0 }
+ }),
+ new ValueSet(4.ToString(), "content",
+ new Dictionary
+ {
+ {"nodeName", "my name 4"},
+ {"nodeTypeAlias", "CWS_Page"},
+ { "longNumber", 4 },
+ { "intNumber", 4 },
+ { "floatNumber", 4.0f },
+ { "doubleNumber", 4.0 }
+ }),
+ });
+
+ var searcher = indexer.Searcher;
+ actAssertAction.Invoke(fieldDefinitionCollection, analyzer, luceneDir, luceneTaxonomyDir, indexer, searcher);
+ }
+ }
}
}