Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: feat: added custom score queries #338

Open
wants to merge 2 commits into
base: release/3.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/Examine.Core/IndexOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ namespace Examine
{
public class IndexOptions
{
public IndexOptions() => FieldDefinitions = new FieldDefinitionCollection();
public IndexOptions() {
FieldDefinitions = new FieldDefinitionCollection();
RelevanceScorerDefinitions = new RelevanceScorerDefinitionCollection();
}

public FieldDefinitionCollection FieldDefinitions { get; set; }
public IValueSetValidator Validator { get; set; }

public RelevanceScorerDefinitionCollection RelevanceScorerDefinitions { get; set; }
}
}
61 changes: 61 additions & 0 deletions src/Examine.Core/ReadOnlyRelevanceScorerDefinitionCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Collections.Concurrent;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Examine
{
public class ReadOnlyRelevanceScorerDefinitionCollection : IEnumerable<RelevanceScorerDefinition>
{
public ReadOnlyRelevanceScorerDefinitionCollection()
: this(Enumerable.Empty<RelevanceScorerDefinition>())
{
}

public ReadOnlyRelevanceScorerDefinitionCollection(params RelevanceScorerDefinition[] definitions)
: this((IEnumerable<RelevanceScorerDefinition>)definitions)
{

}

public ReadOnlyRelevanceScorerDefinitionCollection(IEnumerable<RelevanceScorerDefinition> definitions)
{
if (definitions == null)
{
return;
}

foreach (var s in definitions.GroupBy(x => x.Name))
{
var suggester = s.FirstOrDefault();
if (suggester != default)
{
Definitions.TryAdd(s.Key, suggester);
}
}
}

/// <summary>
/// Tries to get a <see cref="RelevanceScorerDefinition"/> by name
/// </summary>
/// <param name="relevanceScorerName"></param>
/// <param name="relevanceScorerDefinition"></param>
/// <returns>
/// returns true if one was found otherwise false
/// </returns>
/// <remarks>
/// Marked as virtual so developers can inherit this class and override this method in case
/// relevance definitions are dynamic.
/// </remarks>
public virtual bool TryGetValue(string relevanceScorerName, out RelevanceScorerDefinition relevanceScorerDefinition) => Definitions.TryGetValue(relevanceScorerName, out relevanceScorerDefinition);

public int Count => Definitions.Count;

protected ConcurrentDictionary<string, RelevanceScorerDefinition> Definitions { get; } = new ConcurrentDictionary<string, RelevanceScorerDefinition>(StringComparer.InvariantCultureIgnoreCase);

public IEnumerator<RelevanceScorerDefinition> GetEnumerator() => Definitions.Values.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
27 changes: 27 additions & 0 deletions src/Examine.Core/RelevanceScorerDefinition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Collections.Generic;

namespace Examine
{
/// <summary>
/// Defines how to score a document to affect it's relevance
/// </summary>
public class RelevanceScorerDefinition
{
public RelevanceScorerDefinition(string name,
IEnumerable<RelevanceScorerFunctionBaseDefintion> functionScorerDefintions)
{
Name = name;
FunctionScorerDefintions = functionScorerDefintions;
}

/// <summary>
/// Name
/// </summary>
public string Name { get; }

/// <summary>
/// Field Boosting Function Defintions
/// </summary>
public IEnumerable<RelevanceScorerFunctionBaseDefintion> FunctionScorerDefintions { get; }
}
}
25 changes: 25 additions & 0 deletions src/Examine.Core/RelevanceScorerDefinitionCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace Examine
{
public class RelevanceScorerDefinitionCollection : ReadOnlyRelevanceScorerDefinitionCollection
{
public RelevanceScorerDefinitionCollection(params RelevanceScorerDefinition[] definitions) : base(definitions)
{
}

public RelevanceScorerDefinitionCollection()
{
}

public RelevanceScorerDefinition GetOrAdd(string fieldName, Func<string, RelevanceScorerDefinition> add) => Definitions.GetOrAdd(fieldName, add);

/// <summary>
/// Replace any definition with the specified one, if one doesn't exist then it is added
/// </summary>
/// <param name="definition"></param>
public void AddOrUpdate(RelevanceScorerDefinition definition) => Definitions.AddOrUpdate(definition.Name, definition, (s, factory) => definition);

public bool TryAdd(RelevanceScorerDefinition definition) => Definitions.TryAdd(definition.Name, definition);
}
}
29 changes: 29 additions & 0 deletions src/Examine.Core/RelevanceScorerFunctionBaseDefintion.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace Examine
{
/// <summary>
/// Base for Relevance Scorer Functions
/// </summary>
public abstract class RelevanceScorerFunctionBaseDefintion
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="fieldName">Name of the field for the function</param>
/// <param name="boost">Boost for the function</param>
public RelevanceScorerFunctionBaseDefintion(string fieldName, float boost)
{
FieldName = fieldName;
Boost = boost;
}

/// <summary>
/// Name of the field for the function
/// </summary>
public string FieldName { get; }

/// <summary>
/// Boost for the function
/// </summary>
public float Boost { get; }
}
}
26 changes: 26 additions & 0 deletions src/Examine.Core/Scoring/TimeRelevanceScorerFunctionDefintion.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;

namespace Examine.Scoring
{
/// <summary>
/// Boosts relevance based on time recency
/// </summary>
public class TimeRelevanceScorerFunctionDefintion : RelevanceScorerFunctionBaseDefintion
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="fieldName">Name of the field</param>
/// <param name="boost">Boost</param>
/// <param name="boostTimeRange">Duration from current time to boost from <see cref="ExamineClock.CurrentTime"/></param>
public TimeRelevanceScorerFunctionDefintion(string fieldName, float boost, TimeSpan boostTimeRange) : base(fieldName, boost)
{
BoostTimeRange = boostTimeRange;
}

/// <summary>
/// Time range to boost from
/// </summary>
public TimeSpan BoostTimeRange { get; }
}
}
2 changes: 1 addition & 1 deletion src/Examine.Core/Search/IBooleanOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Examine.Search
/// <summary>
/// Defines the supported operation for addition of additional clauses in the fluent API
/// </summary>
public interface IBooleanOperation : IOrdering
public interface IBooleanOperation : IScoreQuery
{
/// <summary>
/// Sets the next operation to be AND
Expand Down
12 changes: 12 additions & 0 deletions src/Examine.Core/Search/IScoreQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;

namespace Examine.Search
{
/// <summary>
/// Defines the supported operation for addition of additional clauses in the fluent API
/// </summary>
public interface IScoreQuery : IOrdering
{
IScoreQuery ScoreWith(params string[] scorers);
}
}
1 change: 1 addition & 0 deletions src/Examine.Lucene/LuceneIndexOptions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using Examine.Lucene.Scoring;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Index;
Expand Down
8 changes: 5 additions & 3 deletions src/Examine.Lucene/Providers/BaseLuceneSearcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using Lucene.Net.Search;
using Examine.Lucene.Search;
using Examine.Search;
using System.Collections.Generic;
using Examine.Lucene.Scoring;

namespace Examine.Lucene.Providers
{
Expand Down Expand Up @@ -60,7 +62,7 @@ public override ISearchResults Search(string searchText, QueryOptions options =

///// <summary>
///// This is NOT used! however I'm leaving this here as example code
/////
/////
///// This is used to recursively set any query type that supports <see cref="MultiTermQuery.RewriteMethod"/> parameters for rewriting
///// before the search executes.
///// </summary>
Expand All @@ -70,10 +72,10 @@ public override ISearchResults Search(string searchText, QueryOptions options =
///// that would need to be set eagerly before any query parsing takes place but if we want to do it lazily here's how.
///// So we need to manually update any query within the outer boolean query with the correct rewrite method, then the underlying LuceneSearcher will call rewrite
///// to update everything.
/////
/////
///// see https://github.com/Shazwazza/Examine/pull/89
///// see https://lists.gt.net/lucene/java-user/92194
/////
/////
///// </remarks>
//private void SetScoringBooleanQueryRewriteMethod(Query query)
//{
Expand Down
32 changes: 19 additions & 13 deletions src/Examine.Lucene/Providers/LuceneIndex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ private LuceneIndex(
//initialize the field types
_fieldValueTypeCollection = new Lazy<FieldValueTypeCollection>(() => CreateFieldValueTypes(_options.IndexValueTypesFactory));

_relevanceScorerDefinitionCollection = new Lazy<RelevanceScorerDefinitionCollection>(() => _options.RelevanceScorerDefinitions);

_searcher = new Lazy<LuceneSearcher>(CreateSearcher);
_cancellationTokenSource = new CancellationTokenSource();
_cancellationToken = _cancellationTokenSource.Token;
Expand All @@ -61,13 +63,13 @@ public LuceneIndex(
: this(loggerFactory, name, (IOptionsMonitor<LuceneIndexOptions>)indexOptions)
{
LuceneDirectoryIndexOptions directoryOptions = indexOptions.GetNamedOptions(name);

if (directoryOptions.DirectoryFactory == null)
{
throw new InvalidOperationException($"No {typeof(IDirectoryFactory)} assigned");
}

_directory = new Lazy<Directory>(() => directoryOptions.DirectoryFactory.CreateDirectory(this, directoryOptions.UnlockIndex));
_directory = new Lazy<Directory>(() => directoryOptions.DirectoryFactory.CreateDirectory(this, directoryOptions.UnlockIndex));
}

//TODO: The problem with this is that the writer would already need to be configured with a PerFieldAnalyzerWrapper
Expand Down Expand Up @@ -137,6 +139,8 @@ internal LuceneIndex(

private readonly Lazy<FieldValueTypeCollection> _fieldValueTypeCollection;

private readonly Lazy<RelevanceScorerDefinitionCollection> _relevanceScorerDefinitionCollection;

// tracks the latest Generation value of what has been indexed.This can be used to force update a searcher to this generation.
private long? _latestGen;

Expand All @@ -147,6 +151,8 @@ internal LuceneIndex(
/// </summary>
public FieldValueTypeCollection FieldValueTypeCollection => _fieldValueTypeCollection.Value;

public RelevanceScorerDefinitionCollection RelevanceScorerDefinitionCollection => _relevanceScorerDefinitionCollection.Value;

/// <summary>
/// The default analyzer to use when indexing content, by default, this is set to StandardAnalyzer
/// </summary>
Expand Down Expand Up @@ -310,7 +316,7 @@ public void EnsureIndex(bool forceOverwrite)
var indexExists = IndexExists();
if (!indexExists || forceOverwrite)
{
//if we can't acquire the lock exit - this will happen if this method is called multiple times but we don't want this
//if we can't acquire the lock exit - this will happen if this method is called multiple times but we don't want this
// logic to actually execute multiple times
if (Monitor.TryEnter(_writerLocker))
{
Expand Down Expand Up @@ -341,12 +347,12 @@ public void EnsureIndex(bool forceOverwrite)
//This will happen if the writer hasn't been created/initialized yet which
// might occur if a rebuild is triggered before any indexing has been triggered.
//In this case we need to initialize a writer and continue as normal.
//Since we are already inside the writer lock and it is null, we are allowed to
//Since we are already inside the writer lock and it is null, we are allowed to
// make this call with out using GetIndexWriter() to do the initialization.
_writer = CreateIndexWriterInternal();
}

//We're forcing an overwrite,
//We're forcing an overwrite,
// this means that we need to cancel all operations currently in place,
// clear the queue and delete all of the data in the index.

Expand Down Expand Up @@ -441,10 +447,10 @@ public override void CreateIndex()
}

/// <summary>
/// Deletes a node from the index.
/// Deletes a node from the index.
/// </summary>
/// <remarks>
/// When a content node is deleted, we also need to delete it's children from the index so we need to perform a
/// When a content node is deleted, we also need to delete it's children from the index so we need to perform a
/// custom Lucene search to find all decendents and create Delete item queues for them too.
/// </remarks>
/// <param name="itemIds">ID of the node to delete</param>
Expand Down Expand Up @@ -675,7 +681,7 @@ private bool DeleteFromIndex(Term indexTerm, bool performCommit = true)
return false;
}
}

/// <summary>
/// Collects the data for the fields and adds the document which is then committed into Lucene.Net's index
/// </summary>
Expand Down Expand Up @@ -823,7 +829,7 @@ public void ScheduleCommit()
// and less than the delay
DateTime.Now - _timestamp < TimeSpan.FromMilliseconds(WaitMilliseconds))
{
//Delay
//Delay
_timer.Change(WaitMilliseconds, 0);
}
else
Expand Down Expand Up @@ -903,7 +909,7 @@ private TrackingIndexWriter CreateIndexWriterInternal()
{
Directory dir = GetLuceneDirectory();

// Unfortunatley if the appdomain is taken down this will remain locked, so we can
// Unfortunatley if the appdomain is taken down this will remain locked, so we can
// ensure that it's unlocked here in that case.
try
{
Expand Down Expand Up @@ -958,8 +964,8 @@ protected virtual IndexWriter CreateIndexWriter(Directory d)
{
System.IO.Directory.CreateDirectory(LuceneIndexFolder.FullName);
_logOutput = new FileStream(Path.Combine(LuceneIndexFolder.FullName, DateTime.UtcNow.ToString("yyyy-MM-dd") + ".log"), FileMode.Append);


}
catch (Exception ex)
{
Expand Down Expand Up @@ -1048,7 +1054,7 @@ private LuceneSearcher CreateSearcher()
// wait for most recent changes when first creating the searcher
WaitForChanges();

return new LuceneSearcher(name + "Searcher", searcherManager, FieldAnalyzer, FieldValueTypeCollection);
return new LuceneSearcher(name + "Searcher", searcherManager, FieldAnalyzer, FieldValueTypeCollection, RelevanceScorerDefinitionCollection);
}

/// <summary>
Expand Down
Loading