Skip to content

Commit

Permalink
Fixes: Numerical range queries don't work when using lucene query syn…
Browse files Browse the repository at this point in the history
…tax #133
  • Loading branch information
Shazwazza committed Nov 7, 2019
1 parent 07a7dd3 commit 506e176
Show file tree
Hide file tree
Showing 15 changed files with 118 additions and 34 deletions.
1 change: 1 addition & 0 deletions src/Examine.Test/Search/FluentApiTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ public void Managed_Full_Text()
indexer1.IndexItem(ValueSet.FromObject("4", "content", new { item1 = "value4", item2 = "Scientists believe the lake could be home to cold-loving microbial life adapted to living in total darkness." }));
indexer1.IndexItem(ValueSet.FromObject("5", "content", new { item1 = "value3", item2 = "Scotch scotch scotch, i love scotch" }));
indexer1.IndexItem(ValueSet.FromObject("6", "content", new { item1 = "value4", item2 = "60% of the time, it works everytime" }));
indexer1.IndexItem(ValueSet.FromObject("7", "content", new { SomeField = "value5", AnotherField = "another value" }));

var searcher = indexer1.GetSearcher();

Expand Down
1 change: 1 addition & 0 deletions src/Examine/Examine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
<Compile Include="LuceneEngine\Indexing\DateTimeType.cs" />
<Compile Include="LuceneEngine\Indexing\DoubleType.cs" />
<Compile Include="LuceneEngine\Indexing\FullTextType.cs" />
<Compile Include="LuceneEngine\Indexing\IndexFieldRangeValueType.cs" />
<Compile Include="LuceneEngine\Indexing\IndexFieldValueTypeBase.cs" />
<Compile Include="LuceneEngine\Indexing\Int32Type.cs" />
<Compile Include="LuceneEngine\Indexing\Int64Type.cs" />
Expand Down
5 changes: 3 additions & 2 deletions src/Examine/LuceneEngine/Indexing/DateTimeType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

namespace Examine.LuceneEngine.Indexing
{
public class DateTimeType : IndexFieldValueTypeBase, IIndexRangeValueType<DateTime>

public class DateTimeType : IndexFieldRangeValueType<DateTime>
{
public DateTools.Resolution Resolution { get; }

Expand Down Expand Up @@ -48,7 +49,7 @@ public override Query GetQuery(string query, Searcher searcher)
return GetQuery(parsedVal, parsedVal);
}

public Query GetQuery(DateTime? lower, DateTime? upper, bool lowerInclusive = true, bool upperInclusive = true)
public override Query GetQuery(DateTime? lower, DateTime? upper, bool lowerInclusive = true, bool upperInclusive = true)
{
return NumericRangeQuery.NewLongRange(FieldName,
lower != null ? DateToLong(lower.Value) : (long?)null,
Expand Down
4 changes: 2 additions & 2 deletions src/Examine/LuceneEngine/Indexing/DoubleType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Examine.LuceneEngine.Indexing
{
public class DoubleType : IndexFieldValueTypeBase, IIndexRangeValueType<double>
public class DoubleType : IndexFieldRangeValueType<double>
{
public DoubleType(string fieldName, bool store= true)
: base(fieldName, store)
Expand All @@ -29,7 +29,7 @@ public override Query GetQuery(string query, Searcher searcher)
return !TryConvert(query, out double parsedVal) ? null : GetQuery(parsedVal, parsedVal);
}

public Query GetQuery(double? lower, double? upper, bool lowerInclusive = true, bool upperInclusive = true)
public override Query GetQuery(double? lower, double? upper, bool lowerInclusive = true, bool upperInclusive = true)
{
return NumericRangeQuery.NewDoubleRange(FieldName,
lower ?? double.MinValue,
Expand Down
6 changes: 6 additions & 0 deletions src/Examine/LuceneEngine/Indexing/FullTextType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ public static Query GenerateQuery(string fieldName, string query, Analyzer analy
var resultQuery = new BooleanQuery();
var phraseQuery = new PhraseQuery { Slop = 0 };

//var phraseQueryTerms = new List<Term>();

//not much to search, only do exact match
if (query.Length < 4)
{
Expand All @@ -96,6 +98,8 @@ public static Query GenerateQuery(string fieldName, string query, Analyzer analy
{
var term = termAttribute.Term;

//phraseQueryTerms.Add(new Term(fieldName, term));
//phraseQuery.Add(new[] { new Term(fieldName, term) });
phraseQuery.Add(new Term(fieldName, term));

var exactMatch = new TermQuery(new Term(fieldName, term));
Expand Down Expand Up @@ -123,6 +127,8 @@ public static Query GenerateQuery(string fieldName, string query, Analyzer analy
}
}

//phraseQuery.Add(phraseQueryTerms.ToArray());

return resultQuery.Clauses.Count > 0 ? resultQuery : null;
}

Expand Down
12 changes: 12 additions & 0 deletions src/Examine/LuceneEngine/Indexing/IIndexRangeValueType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

namespace Examine.LuceneEngine.Indexing
{
/// <summary>
/// Used for value range types when the type requires parsing from string
/// </summary>
public interface IIndexRangeValueType
{
Query GetQuery(string lower, string upper, bool lowerInclusive = true, bool upperInclusive = true);
}

/// <summary>
/// Used for value range types when the type is known
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IIndexRangeValueType<T> where T : struct
{
Query GetQuery(T? lower, T? upper, bool lowerInclusive = true, bool upperInclusive = true);
Expand Down
22 changes: 22 additions & 0 deletions src/Examine/LuceneEngine/Indexing/IndexFieldRangeValueType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Lucene.Net.Search;

namespace Examine.LuceneEngine.Indexing
{
public abstract class IndexFieldRangeValueType<T> : IndexFieldValueTypeBase, IIndexRangeValueType<T>, IIndexRangeValueType
where T : struct
{
protected IndexFieldRangeValueType(string fieldName, bool store = true) : base(fieldName, store)
{
}

public abstract Query GetQuery(T? lower, T? upper, bool lowerInclusive = true, bool upperInclusive = true);

public Query GetQuery(string lower, string upper, bool lowerInclusive = true, bool upperInclusive = true)
{
var lowerParsed = TryConvert<T>(lower, out var lowerValue);
var upperParsed = TryConvert<T>(upper, out var upperValue);

return GetQuery(lowerParsed ? (T?)lowerValue : null, upperParsed ? (T?)upperValue : null, lowerInclusive, upperInclusive);
}
}
}
4 changes: 2 additions & 2 deletions src/Examine/LuceneEngine/Indexing/Int32Type.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Examine.LuceneEngine.Indexing
{
public class Int32Type : IndexFieldValueTypeBase, IIndexRangeValueType<int>
public class Int32Type : IndexFieldRangeValueType<int>
{
public Int32Type(string fieldName, bool store = true)
: base(fieldName, store)
Expand All @@ -29,7 +29,7 @@ public override Query GetQuery(string query, Searcher searcher)
return !TryConvert(query, out int parsedVal) ? null : GetQuery(parsedVal, parsedVal);
}

public Query GetQuery(int? lower, int? upper, bool lowerInclusive = true, bool upperInclusive = true)
public override Query GetQuery(int? lower, int? upper, bool lowerInclusive = true, bool upperInclusive = true)
{
return NumericRangeQuery.NewIntRange(FieldName,
lower,
Expand Down
4 changes: 2 additions & 2 deletions src/Examine/LuceneEngine/Indexing/Int64Type.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Examine.LuceneEngine.Indexing
{
public class Int64Type : IndexFieldValueTypeBase, IIndexRangeValueType<long>
public class Int64Type : IndexFieldRangeValueType<long>
{
public Int64Type(string fieldName, bool store = true)
: base(fieldName, store)
Expand All @@ -29,7 +29,7 @@ public override Query GetQuery(string query, Searcher searcher)
return !TryConvert(query, out long parsedVal) ? null : GetQuery(parsedVal, parsedVal);
}

public Query GetQuery(long? lower, long? upper, bool lowerInclusive = true, bool upperInclusive = true)
public override Query GetQuery(long? lower, long? upper, bool lowerInclusive = true, bool upperInclusive = true)
{
return NumericRangeQuery.NewLongRange(FieldName,
lower,
Expand Down
4 changes: 2 additions & 2 deletions src/Examine/LuceneEngine/Indexing/SingleType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Examine.LuceneEngine.Indexing
{
public class SingleType : IndexFieldValueTypeBase, IIndexRangeValueType<float>
public class SingleType : IndexFieldRangeValueType<float>
{
public SingleType(string fieldName, bool store = true)
: base(fieldName, store)
Expand All @@ -29,7 +29,7 @@ public override Query GetQuery(string query, Searcher searcher)
return !TryConvert(query, out float parsedVal) ? null : GetQuery(parsedVal, parsedVal);
}

public Query GetQuery(float? lower, float? upper, bool lowerInclusive = true, bool upperInclusive = true)
public override Query GetQuery(float? lower, float? upper, bool lowerInclusive = true, bool upperInclusive = true)
{
return NumericRangeQuery.NewFloatRange(FieldName,
lower ?? float.MinValue,
Expand Down
22 changes: 1 addition & 21 deletions src/Examine/LuceneEngine/Search/CustomMultiFieldQueryParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

namespace Examine.LuceneEngine.Search
{

/// <summary>
/// We use this to get at the protected methods directly since the new version makes them not public
/// </summary>
Expand All @@ -15,27 +16,6 @@ public CustomMultiFieldQueryParser(Version matchVersion, string[] fields, Analyz
{
}

/// <summary>
/// Override to provide support for numerical range query parsing
/// </summary>
/// <param name="field"></param>
/// <param name="part1"></param>
/// <param name="part2"></param>
/// <param name="inclusive"></param>
/// <returns></returns>
/// <remarks>
/// By Default the lucene query parser only deals with strings and the result is a TermRangeQuery, however for numerics it needs to be a
/// NumericRangeQuery. We can override this method to provide that behavior.
///
/// In previous releases people were complaining that this wouldn't work and this is why. The answer came from here https://stackoverflow.com/questions/5026185/how-do-i-make-the-queryparser-in-lucene-handle-numeric-ranges
///
/// TODO: We could go further and override the field query and check if it is a numeric field, if so then we can automatically generate a numeric range query for the single digit too.
/// </remarks>
protected override Query GetRangeQuery(string field, string part1, string part2, bool inclusive)
{
return base.GetRangeQuery(field, part1, part2, inclusive);
}

public Query GetFuzzyQueryInternal(string field, string termStr, float minSimilarity)
{
return GetFuzzyQuery(field, termStr, minSimilarity);
Expand Down
46 changes: 46 additions & 0 deletions src/Examine/LuceneEngine/Search/ExamineMultiFieldQueryParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Examine.LuceneEngine.Indexing;
using Lucene.Net.Analysis;
using Lucene.Net.Search;
using Lucene.Net.Util;

namespace Examine.LuceneEngine.Search
{
/// <summary>
/// Custom query parser to deal with Examine/Lucene field value types
/// </summary>
public class ExamineMultiFieldQueryParser : CustomMultiFieldQueryParser
{
private readonly ISearchContext _searchContext;

public ExamineMultiFieldQueryParser(ISearchContext searchContext, Version matchVersion, string[] fields, Analyzer analyzer) : base(matchVersion, fields, analyzer)
{
_searchContext = searchContext ?? throw new System.ArgumentNullException(nameof(searchContext));
}

/// <summary>
/// Override to provide support for numerical range query parsing
/// </summary>
/// <param name="field"></param>
/// <param name="part1"></param>
/// <param name="part2"></param>
/// <param name="inclusive"></param>
/// <returns></returns>
/// <remarks>
/// By Default the lucene query parser only deals with strings and the result is a TermRangeQuery, however for numerics it needs to be a
/// NumericRangeQuery. We can override this method to provide that behavior.
///
/// In previous releases people were complaining that this wouldn't work and this is why. The answer came from here https://stackoverflow.com/questions/5026185/how-do-i-make-the-queryparser-in-lucene-handle-numeric-ranges
/// </remarks>
protected override Query GetRangeQuery(string field, string part1, string part2, bool inclusive)
{
// if the field is IIndexRangeValueType then return it's query, else return the default
var fieldType = _searchContext.GetFieldValueType(field);
if (fieldType != null && fieldType is IIndexRangeValueType rangeType)
{
return rangeType.GetQuery(part1, part2, inclusive, inclusive);
}

return base.GetRangeQuery(field, part1, part2, inclusive);
}
}
}
2 changes: 1 addition & 1 deletion src/Examine/LuceneEngine/Search/LateBoundQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace Examine.LuceneEngine.Search
{
internal class LateBoundQuery : Query
public class LateBoundQuery : Query
{
private readonly Func<Query> _factory;

Expand Down
5 changes: 4 additions & 1 deletion src/Examine/LuceneEngine/Search/LuceneSearchQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ public class LuceneSearchQuery : LuceneSearchQueryBase, IQueryExecutor
public LuceneSearchQuery(
ISearchContext searchContext,
string category, Analyzer analyzer, string[] fields, LuceneSearchOptions searchOptions, BooleanOperation occurance)
: base(category, analyzer, fields, searchOptions, occurance)
: base(CreateQueryParser(searchContext, fields, analyzer), category, fields, searchOptions, occurance)
{
_searchContext = searchContext;
}

private static CustomMultiFieldQueryParser CreateQueryParser(ISearchContext searchContext, string[] fields, Analyzer analyzer)
=> new ExamineMultiFieldQueryParser(searchContext, LuceneVersion, fields, analyzer);

public IBooleanOperation OrderBy(params SortableField[] fields) => OrderByInternal(false, fields);

public IBooleanOperation OrderByDescending(params SortableField[] fields) => OrderByInternal(true, fields);
Expand Down
14 changes: 13 additions & 1 deletion src/Examine/LuceneEngine/Search/LuceneSearchQueryBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,20 @@ public abstract class LuceneSearchQueryBase : IQuery, INestedQuery
protected Occur Occurrence;
private BooleanOperation _boolOp;

private const Version LuceneVersion = Version.LUCENE_30;
public const Version LuceneVersion = Version.LUCENE_30;

protected LuceneSearchQueryBase(CustomMultiFieldQueryParser queryParser,
string category, string[] fields, LuceneSearchOptions searchOptions, BooleanOperation occurance)
{
Category = category;
AllFields = fields ?? throw new ArgumentNullException(nameof(fields));
SearchOptions = searchOptions;
Queries.Push(new BooleanQuery());
BooleanOperation = occurance;
_queryParser = queryParser;
}

[Obsolete("Use the ctor specifying a query parser instead")]
protected LuceneSearchQueryBase(
string category, Analyzer analyzer, string[] fields, LuceneSearchOptions searchOptions, BooleanOperation occurance)
{
Expand Down

0 comments on commit 506e176

Please sign in to comment.