diff --git a/docs/articles/filtering.md b/docs/articles/filtering.md
new file mode 100644
index 00000000..85b9a6c7
--- /dev/null
+++ b/docs/articles/filtering.md
@@ -0,0 +1,235 @@
+---
+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 and Chains
+
+### 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();
+```
+
+### Chaining
+
+```csharp
+var searcher = myIndex.Searcher;
+var results = searcher.CreateQuery()
+ .WithFilter(
+ filter =>
+ {
+ filter.ChainFilters(chain =>
+ chain.Chain(ChainOperation.AND, chainedFilter => chainedFilter.NestedFieldValueExists("nodeTypeAlias")) //AND
+ .Chain(ChainOperation.AND, chainedFilter => chainedFilter.NestedTermPrefix(new FilterTerm("nodeTypeAlias", "CWS_H")))
+ .Chain(ChainOperation.OR, chainedFilter => chainedFilter.NestedTermFilter(new FilterTerm("nodeName", "my name")))
+ .Chain(ChainOperation.ANDNOT, chainedFilter => chainedFilter.NestedTermFilter(new FilterTerm("nodeName", "someone elses name")))
+ .Chain(ChainOperation.XOR, chainedFilter => chainedFilter.NestedTermPrefix(new FilterTerm("nodeName", "my")))
+ );
+ })
+ .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/ChainOperation.cs b/src/Examine.Core/Search/ChainOperation.cs
new file mode 100644
index 00000000..3ea059da
--- /dev/null
+++ b/src/Examine.Core/Search/ChainOperation.cs
@@ -0,0 +1,25 @@
+namespace Examine.Search
+{
+ ///
+ /// Represents types of chain operations
+ ///
+ public enum ChainOperation
+ {
+ ///
+ /// Or
+ ///
+ OR = 0,
+ ///
+ /// And
+ ///
+ AND = 1,
+ ///
+ /// And Not
+ ///
+ ANDNOT = 2,
+ ///
+ /// Exclusive Or
+ ///
+ XOR = 3
+ }
+}
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..a647170c
--- /dev/null
+++ b/src/Examine.Core/Search/IFilter.cs
@@ -0,0 +1,109 @@
+using System;
+using System.Collections.Generic;
+
+namespace Examine.Search
+{
+ public interface IFilter
+ {
+ ///
+ /// Chain filters
+ ///
+ ///
+ ///
+ IBooleanFilterOperation ChainFilters(Action chain);
+
+ ///
+ /// 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/IFilterChain.cs b/src/Examine.Core/Search/IFilterChain.cs
new file mode 100644
index 00000000..c4b8a739
--- /dev/null
+++ b/src/Examine.Core/Search/IFilterChain.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace Examine.Search
+{
+ ///
+ /// Filter Chaining
+ ///
+ public interface IFilterChain
+ {
+ ///
+ /// Chain Filter AND
+ ///
+ /// First Filter in the Chain
+ ///
+ IFilterChain Chain(Func nextFilter);
+
+ ///
+ /// Chain Filter
+ ///
+ /// Next Filter in the Chain
+ /// Operation between the filter in the chain
+ ///
+ IFilterChain Chain(ChainOperation operation, Func nextFilter);
+ }
+}
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..ca716ae3
--- /dev/null
+++ b/src/Examine.Core/Search/INestedFilter.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+
+namespace Examine.Search
+{
+ public interface INestedFilter
+ {
+ ///
+ /// Chain filters
+ ///
+ ///
+ ///
+ INestedBooleanFilterOperation NestedChainFilters(Action chain);
+
+ ///
+ /// 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/FilterChain.cs b/src/Examine.Lucene/Search/FilterChain.cs
new file mode 100644
index 00000000..d5421ad3
--- /dev/null
+++ b/src/Examine.Lucene/Search/FilterChain.cs
@@ -0,0 +1,32 @@
+using Lucene.Net.Search;
+
+namespace Examine.Lucene.Search
+{
+ ///
+ /// Filter Chain
+ ///
+ public struct FilterChain
+ {
+ ///
+ /// Constructor
+ ///
+ ///
+ ///
+ public FilterChain(Filter filter, int operation)
+ {
+ Filter = filter;
+ Operation = operation;
+ }
+
+ ///
+ /// Filter
+ ///
+ public Filter Filter { get; }
+
+ ///
+ /// Filter Chain Operation
+ ///
+ public int Operation { get; }
+
+ }
+}
diff --git a/src/Examine.Lucene/Search/FilterChainOp.cs b/src/Examine.Lucene/Search/FilterChainOp.cs
new file mode 100644
index 00000000..d6f9f73c
--- /dev/null
+++ b/src/Examine.Lucene/Search/FilterChainOp.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using Examine.Search;
+
+namespace Examine.Lucene.Search
+{
+ ///
+ /// Filter Chain Operation
+ ///
+ public class FilterChainOp : FilterChainOpBase, IFilterChain
+ {
+ private readonly LuceneSearchFilteringOperation _luceneFilter;
+
+ ///
+ /// Constructor
+ ///
+ ///
+ public FilterChainOp(LuceneSearchFilteringOperation luceneFilter)
+ {
+ ChainOps = new Queue();
+ _luceneFilter = luceneFilter;
+ }
+
+ ///
+ public override IFilterChain Chain(Func nextFilter)
+ {
+ var filterOp = new LuceneSearchFilteringOperation(_luceneFilter.LuceneSearchQuery);
+ var bo = new LuceneBooleanOperation(_luceneFilter.LuceneSearchQuery);
+ var fbo = new LuceneFilteringBooleanOperation(filterOp);
+ var filter = fbo.GetNestedFilterOp(nextFilter, BooleanOperation.And);
+ // Start a chain
+ ChainOps.Enqueue(new FilterChain(filter, (int)ChainOperation.AND));
+ return this;
+ }
+
+ ///
+ public override IFilterChain Chain(ChainOperation operation, Func nextFilter)
+ {
+ var filterOp = new LuceneSearchFilteringOperation(_luceneFilter.LuceneSearchQuery);
+ var bo = new LuceneBooleanOperation(_luceneFilter.LuceneSearchQuery);
+ var fbo = new LuceneFilteringBooleanOperation(filterOp);
+ var filter = fbo.GetNestedFilterOp(nextFilter, BooleanOperation.And);
+ // Continue a chain
+ ChainOps.Enqueue(new FilterChain(filter, (int)operation));
+ return this;
+ }
+ }
+}
diff --git a/src/Examine.Lucene/Search/FilterChainOpBase.cs b/src/Examine.Lucene/Search/FilterChainOpBase.cs
new file mode 100644
index 00000000..b7af6842
--- /dev/null
+++ b/src/Examine.Lucene/Search/FilterChainOpBase.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using Examine.Search;
+using Lucene.Net.Queries;
+using Lucene.Net.Search;
+
+namespace Examine.Lucene.Search
+{
+ ///
+ /// Filter Chain Operation
+ ///
+ public abstract class FilterChainOpBase : IFilterChain
+ {
+ ///
+ /// Constructor
+ ///
+ public FilterChainOpBase()
+ {
+ ChainOps = new Queue ();
+ }
+
+ ///
+ /// Chained Filter Operations
+ ///
+ public Queue ChainOps { get; set; }
+
+ ///
+ /// Build Chained Filter
+ ///
+ ///
+ public ChainedFilter Build()
+ {
+ var count = ChainOps.Count;
+ var filters = new Filter[count];
+ int[] logicArray = new int[count];
+ for (int i = 0; i < count; i++)
+ {
+ var fc = ChainOps.Dequeue();
+ filters[i] = fc.Filter;
+ logicArray[i] = fc.Operation;
+ }
+
+ var chainedFilter = new ChainedFilter(filters, logicArray);
+ return chainedFilter;
+ }
+
+ public abstract IFilterChain Chain(ChainOperation operation, Func nextFilter);
+
+ public abstract IFilterChain Chain(Func nextFilter);
+ }
+}
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..0b9619d3
--- /dev/null
+++ b/src/Examine.Lucene/Search/LuceneFilter.cs
@@ -0,0 +1,82 @@
+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 ChainFilters(Action chain) => _search.ChainFiltersInternal(chain, _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);
+
+ ///
+ INestedBooleanFilterOperation INestedFilter.NestedChainFilters(Action chain) => _search.NestedChainFiltersInternal(chain, _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..19c7e75c
--- /dev/null
+++ b/src/Examine.Lucene/Search/LuceneFilteringBooleanOperation.cs
@@ -0,0 +1,86 @@
+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 ChainFilters(Action chain) => _search.ChainFilters(chain);
+
+ ///
+ 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..abaa258d
--- /dev/null
+++ b/src/Examine.Lucene/Search/LuceneFilteringBooleanOperationBase.cs
@@ -0,0 +1,169 @@
+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 ChainFilters(Action chain);
+
+ ///
+ 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..145836cf
--- /dev/null
+++ b/src/Examine.Lucene/Search/LuceneSearchFiltering.cs
@@ -0,0 +1,450 @@
+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);
+
+ ///
+ /// Creates a new
+ ///
+ ///
+ protected override FilterChainOpBase CreateChainOp() => new FilterChainOp(this);
+ #region IFilter
+
+ ///
+ public override IBooleanFilterOperation ChainFilters(Action chain)
+ {
+ return ChainFiltersInternal(chain);
+ }
+
+ ///
+ internal IBooleanFilterOperation ChainFiltersInternal(Action chain, Occur occurance = Occur.MUST)
+ {
+ if (chain is null)
+ {
+ throw new ArgumentNullException(nameof(chain));
+ }
+ var chaining = CreateChainOp();
+ chain.Invoke(chaining);
+ var chainedFilter = chaining.Build();
+ if (chainedFilter != null)
+ {
+ Filter.Add(chainedFilter, occurance);
+ }
+
+ return CreateBooleanOp();
+ }
+
+ ///
+ 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 NestedChainFilters(Action chain) => NestedChainFiltersInternal(chain);
+
+ ///
+ 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 NestedChainFiltersInternal(Action chain, Occur occurance = Occur.MUST)
+ {
+ if (chain is null)
+ {
+ throw new ArgumentNullException(nameof(chain));
+ }
+ var chaining = new FilterChainOp(this);
+ chain.Invoke(chaining);
+ var chainedFilter = chaining.Build();
+ if (chainedFilter != null)
+ {
+ Filter.Add(chainedFilter, occurance);
+ }
+
+ return CreateBooleanOp();
+ }
+
+ ///
+ 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..72e47280
--- /dev/null
+++ b/src/Examine.Lucene/Search/LuceneSearchFilteringOperationBase.cs
@@ -0,0 +1,152 @@
+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();
+
+ ///
+ /// Creates a new
+ ///
+ ///
+ protected abstract FilterChainOpBase CreateChainOp();
+
+ ///
+ public abstract IBooleanFilterOperation ChainFilters(Action chain);
+
+ ///
+ 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 NestedChainFilters(Action chain);
+
+ ///
+ 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.NestedChainFilters(Action chain) => NestedChainFilters(chain);
+
+ ///
+ 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..4be68f1b 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,605 @@ 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 ChainedFilters(FacetTestType withFacets)
+ {
+ Action actAssertAction
+ = (fieldDefinitionCollection, indexAnalyzer, indexDirectory, taxonomyDirectory, testIndex, searcher)
+ =>
+ {
+
+ var criteria = searcher.CreateQuery("content")
+ .WithFilter(
+ filter =>
+ {
+ filter.ChainFilters(chain =>
+ chain.Chain(ChainOperation.OR, chainedFilter => chainedFilter.NestedFieldValueExists("nodeTypeAlias"))
+ .Chain(ChainOperation.AND, chainedFilter => chainedFilter.NestedTermPrefix(new FilterTerm("nodeTypeAlias", "CWS_H")))
+ .Chain(ChainOperation.OR, chainedFilter => chainedFilter.NestedTermFilter(new FilterTerm("nodeName", "my name")))
+ .Chain(ChainOperation.ANDNOT, chainedFilter => chainedFilter.NestedTermFilter(new FilterTerm("nodeName", "someone elses name")))
+ .Chain(ChainOperation.XOR, chainedFilter => chainedFilter.NestedTermPrefix(new FilterTerm("nodeName", "my")))
+ );
+ });
+ 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 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);
+ }
+ }
}
}