mirror of https://github.com/ErsatzTV/ErsatzTV.git
Browse Source
* add search query parser * add some search tests; use consistent analyzers for indexing and searchingpull/1653/head
10 changed files with 126 additions and 52 deletions
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
using ErsatzTV.Infrastructure.Search; |
||||
using FluentAssertions; |
||||
using Lucene.Net.Search; |
||||
using NUnit.Framework; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Tests.Search; |
||||
|
||||
public class SearchQueryParserTests |
||||
{ |
||||
[TestFixture] |
||||
public class ParseQuery |
||||
{ |
||||
[TestCase("actor:\"Will Smith\"", "actor:\"will smith\"")] |
||||
[TestCase("tag:\"Will Smith\"", "tag:\"will smith\"")] |
||||
[TestCase("library_id:4", "library_id:4")] |
||||
[TestCase("content_rating:\"TV-14\"", "content_rating:TV-14")] |
||||
[TestCase("content_rating:\"test PHRASE\"", "content_rating:\"test PHRASE\"")] |
||||
public void Test(string input, string expected) |
||||
{ |
||||
Query result = SearchQueryParser.ParseQuery(input); |
||||
result.ToString().Should().Be(expected); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
using Lucene.Net.Analysis; |
||||
using Lucene.Net.Analysis.Core; |
||||
using Lucene.Net.Util; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Search; |
||||
|
||||
public sealed class CustomAnalyzer(LuceneVersion matchVersion) : Analyzer |
||||
{ |
||||
protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader) |
||||
{ |
||||
Tokenizer tokenizer = new WhitespaceTokenizer(matchVersion, reader); |
||||
TokenStream result = new LowerCaseFilter(matchVersion, tokenizer); |
||||
return new TokenStreamComponents(tokenizer, result); |
||||
} |
||||
} |
||||
@ -0,0 +1,71 @@
@@ -0,0 +1,71 @@
|
||||
using Lucene.Net.Analysis; |
||||
using Lucene.Net.Analysis.Core; |
||||
using Lucene.Net.Analysis.Miscellaneous; |
||||
using Lucene.Net.Analysis.Standard; |
||||
using Lucene.Net.QueryParsers.Classic; |
||||
using Query = Lucene.Net.Search.Query; |
||||
|
||||
namespace ErsatzTV.Infrastructure.Search; |
||||
|
||||
public static class SearchQueryParser |
||||
{ |
||||
internal static Analyzer AnalyzerWrapper() |
||||
{ |
||||
using var defaultAnalyzer = new CustomAnalyzer(LuceneSearchIndex.AppLuceneVersion); |
||||
using var keywordAnalyzer = new KeywordAnalyzer(); |
||||
var customAnalyzers = new Dictionary<string, Analyzer> |
||||
{ |
||||
// StringField should use KeywordAnalyzer
|
||||
{ LuceneSearchIndex.IdField, keywordAnalyzer }, |
||||
{ LuceneSearchIndex.TypeField, keywordAnalyzer }, |
||||
{ LuceneSearchIndex.SortTitleField, keywordAnalyzer }, |
||||
{ LuceneSearchIndex.LibraryIdField, keywordAnalyzer }, |
||||
{ LuceneSearchIndex.TitleAndYearField, keywordAnalyzer }, |
||||
{ LuceneSearchIndex.JumpLetterField, keywordAnalyzer }, |
||||
{ LuceneSearchIndex.StateField, keywordAnalyzer }, |
||||
{ LuceneSearchIndex.ContentRatingField, keywordAnalyzer }, |
||||
{ LuceneSearchIndex.ReleaseDateField, keywordAnalyzer }, |
||||
{ LuceneSearchIndex.AddedDateField, keywordAnalyzer }, |
||||
{ LuceneSearchIndex.TraktListField, keywordAnalyzer }, |
||||
{ LuceneSearchIndex.ShowContentRatingField, keywordAnalyzer }, |
||||
{ LuceneSearchIndex.LibraryFolderIdField, keywordAnalyzer }, |
||||
{ LuceneSearchIndex.VideoCodecField, keywordAnalyzer }, |
||||
{ LuceneSearchIndex.VideoDynamicRange, keywordAnalyzer }, |
||||
|
||||
{ LuceneSearchIndex.PlotField, new StandardAnalyzer(LuceneSearchIndex.AppLuceneVersion) } |
||||
}; |
||||
|
||||
return new PerFieldAnalyzerWrapper(defaultAnalyzer, customAnalyzers); |
||||
} |
||||
|
||||
public static Query ParseQuery(string query) |
||||
{ |
||||
using Analyzer analyzerWrapper = AnalyzerWrapper(); |
||||
|
||||
QueryParser parser = new CustomMultiFieldQueryParser( |
||||
LuceneSearchIndex.AppLuceneVersion, |
||||
[LuceneSearchIndex.TitleField], |
||||
analyzerWrapper); |
||||
parser.AllowLeadingWildcard = true; |
||||
Query result = ParseQuery(query, parser); |
||||
|
||||
Serilog.Log.Logger.Debug("Search query parsed from [{Query}] to [{ParsedQuery}]", query, result.ToString()); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
private static Query ParseQuery(string searchQuery, QueryParser parser) |
||||
{ |
||||
Query query; |
||||
try |
||||
{ |
||||
query = parser.Parse(searchQuery.Trim()); |
||||
} |
||||
catch (ParseException) |
||||
{ |
||||
query = parser.Parse(QueryParserBase.Escape(searchQuery.Trim())); |
||||
} |
||||
|
||||
return query; |
||||
} |
||||
} |
||||
Loading…
Reference in new issue