mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
Update ISearchBuilder to pass the string unprocessed to Lucene
--HG-- branch : dev
This commit is contained in:
@@ -3,17 +3,13 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Autofac;
|
using Autofac;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Orchard.Environment;
|
|
||||||
using Orchard.Environment.Configuration;
|
using Orchard.Environment.Configuration;
|
||||||
using Orchard.FileSystems.AppData;
|
using Orchard.FileSystems.AppData;
|
||||||
using Orchard.FileSystems.VirtualPath;
|
|
||||||
using Orchard.Indexing;
|
using Orchard.Indexing;
|
||||||
using Orchard.Core.Indexing.Lucene;
|
using Orchard.Core.Indexing.Lucene;
|
||||||
using Orchard.Services;
|
|
||||||
using Orchard.Tests.Environment.Configuration;
|
|
||||||
using Orchard.Tests.FileSystems.AppData;
|
using Orchard.Tests.FileSystems.AppData;
|
||||||
|
|
||||||
namespace Orchard.Tests.Indexing {
|
namespace Orchard.Core.Tests.Indexing {
|
||||||
public class DefaultSearchBuilderTests {
|
public class DefaultSearchBuilderTests {
|
||||||
private IContainer _container;
|
private IContainer _container;
|
||||||
private IIndexProvider _provider;
|
private IIndexProvider _provider;
|
||||||
@@ -194,5 +190,57 @@ namespace Orchard.Tests.Indexing {
|
|||||||
Assert.That(cpp.Count(), Is.EqualTo(2));
|
Assert.That(cpp.Count(), Is.EqualTo(2));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ShouldHandleMandatoryFields() {
|
||||||
|
_provider.CreateIndex("default");
|
||||||
|
_provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#"));
|
||||||
|
_provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++"));
|
||||||
|
|
||||||
|
Assert.That(_searchBuilder.WithField("body", "develop").Search().ToList().Count(), Is.EqualTo(2));
|
||||||
|
Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Search().ToList().Count(), Is.EqualTo(2));
|
||||||
|
Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Mandatory().Search().ToList().Count(), Is.EqualTo(1));
|
||||||
|
Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Mandatory().Search().First().Id, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ShouldHandleForbiddenFields() {
|
||||||
|
_provider.CreateIndex("default");
|
||||||
|
_provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#"));
|
||||||
|
_provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++"));
|
||||||
|
|
||||||
|
Assert.That(_searchBuilder.WithField("body", "develop").Search().ToList().Count(), Is.EqualTo(2));
|
||||||
|
Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Search().ToList().Count(), Is.EqualTo(2));
|
||||||
|
Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Forbidden().Search().ToList().Count(), Is.EqualTo(1));
|
||||||
|
Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Forbidden().Search().First().Id, Is.EqualTo(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ShouldHandleWeight() {
|
||||||
|
_provider.CreateIndex("default");
|
||||||
|
_provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#"));
|
||||||
|
_provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++"));
|
||||||
|
|
||||||
|
Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Weighted(2).Search().First().Id, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ShouldParseLuceneQueries() {
|
||||||
|
_provider.CreateIndex("default");
|
||||||
|
_provider.Store("default", _provider.New(1).Add("body", "Bradley is in the kitchen.").Add("title", "Beer and takos"));
|
||||||
|
_provider.Store("default", _provider.New(2).Add("body", "Renaud is also in the kitchen.").Add("title", "A love affair"));
|
||||||
|
_provider.Store("default", _provider.New(3).Add("body", "Bertrand is a little bit jealous.").Add("title", "Soap opera"));
|
||||||
|
|
||||||
|
Assert.That(_searchBuilder.Parse(new[] { "body" }, "kitchen").Count(), Is.EqualTo(2));
|
||||||
|
Assert.That(_searchBuilder.Parse(new[] { "body" }, "kitchen bertrand").Count(), Is.EqualTo(3));
|
||||||
|
Assert.That(_searchBuilder.Parse(new[] { "body" }, "kitchen +bertrand").Count(), Is.EqualTo(1));
|
||||||
|
Assert.That(_searchBuilder.Parse(new[] { "body" }, "+kitchen +bertrand").Count(), Is.EqualTo(0));
|
||||||
|
Assert.That(_searchBuilder.Parse(new[] { "body" }, "kit").Count(), Is.EqualTo(0));
|
||||||
|
Assert.That(_searchBuilder.Parse(new[] { "body" }, "kit*").Count(), Is.EqualTo(2));
|
||||||
|
Assert.That(_searchBuilder.Parse(new[] { "body", "title" }, "bradley love^3 soap").Count(), Is.EqualTo(3));
|
||||||
|
Assert.That(_searchBuilder.Parse(new[] { "body", "title" }, "bradley love^3 soap").Search().First().Id, Is.EqualTo(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace Orchard.Core.Indexing.Lucene {
|
|||||||
|
|
||||||
private readonly Directory _directory;
|
private readonly Directory _directory;
|
||||||
|
|
||||||
private readonly Dictionary<string, Query[]> _fields;
|
private readonly Dictionary<string, BooleanClause[]> _fields;
|
||||||
private int _count;
|
private int _count;
|
||||||
private int _skip;
|
private int _skip;
|
||||||
private readonly Dictionary<string, DateTime> _before;
|
private readonly Dictionary<string, DateTime> _before;
|
||||||
@@ -28,7 +28,15 @@ namespace Orchard.Core.Indexing.Lucene {
|
|||||||
private bool _sortDescending;
|
private bool _sortDescending;
|
||||||
private string _parse;
|
private string _parse;
|
||||||
private readonly Analyzer _analyzer;
|
private readonly Analyzer _analyzer;
|
||||||
private string _defaultField;
|
private string[] _defaultFields;
|
||||||
|
|
||||||
|
// pending clause attributes
|
||||||
|
private string _field;
|
||||||
|
private string _terms;
|
||||||
|
private BooleanClause.Occur _occur;
|
||||||
|
private bool _prefix;
|
||||||
|
private bool _stem;
|
||||||
|
private float _boost;
|
||||||
|
|
||||||
public ILogger Logger { get; set; }
|
public ILogger Logger { get; set; }
|
||||||
|
|
||||||
@@ -40,15 +48,17 @@ namespace Orchard.Core.Indexing.Lucene {
|
|||||||
_skip = 0;
|
_skip = 0;
|
||||||
_before = new Dictionary<string, DateTime>();
|
_before = new Dictionary<string, DateTime>();
|
||||||
_after = new Dictionary<string, DateTime>();
|
_after = new Dictionary<string, DateTime>();
|
||||||
_fields = new Dictionary<string, Query[]>();
|
_fields = new Dictionary<string, BooleanClause[]>();
|
||||||
_sort = String.Empty;
|
_sort = String.Empty;
|
||||||
_sortDescending = true;
|
_sortDescending = true;
|
||||||
_parse = String.Empty;
|
_parse = String.Empty;
|
||||||
_analyzer = DefaultIndexProvider.CreateAnalyzer();
|
_analyzer = DefaultIndexProvider.CreateAnalyzer();
|
||||||
|
|
||||||
|
InitPendingClause();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ISearchBuilder Parse(string defaultField, string query) {
|
public ISearchBuilder Parse(string[] defaultFields, string query) {
|
||||||
if ( String.IsNullOrWhiteSpace(defaultField) ) {
|
if ( defaultFields.Length == 0 ) {
|
||||||
throw new ArgumentException("Default field can't be empty");
|
throw new ArgumentException("Default field can't be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,32 +66,84 @@ namespace Orchard.Core.Indexing.Lucene {
|
|||||||
throw new ArgumentException("Query can't be empty");
|
throw new ArgumentException("Query can't be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
_defaultField = defaultField;
|
_defaultFields = defaultFields;
|
||||||
_parse = query;
|
_parse = query;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ISearchBuilder WithField(string field, string value) {
|
public ISearchBuilder WithField(string field, string value) {
|
||||||
return WithField(field, value, true);
|
CreatePendingClause();
|
||||||
|
|
||||||
|
_field = field;
|
||||||
|
_terms = value;
|
||||||
|
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ISearchBuilder WithField(string field, string value, bool wildcardSearch) {
|
public ISearchBuilder Mandatory() {
|
||||||
|
_occur = BooleanClause.Occur.MUST;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISearchBuilder Forbidden() {
|
||||||
|
_occur = BooleanClause.Occur.MUST_NOT;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISearchBuilder ExactMatch() {
|
||||||
|
_prefix = false;
|
||||||
|
_stem = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISearchBuilder Weighted(float weight) {
|
||||||
|
_boost = weight;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitPendingClause() {
|
||||||
|
_field = String.Empty;
|
||||||
|
_terms = String.Empty;
|
||||||
|
_occur = BooleanClause.Occur.SHOULD;
|
||||||
|
_prefix = true;
|
||||||
|
_stem = true;
|
||||||
|
_boost = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreatePendingClause() {
|
||||||
|
if(String.IsNullOrWhiteSpace(_field) || String.IsNullOrWhiteSpace(_terms)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var tokens = new List<string>();
|
var tokens = new List<string>();
|
||||||
using(var sr = new System.IO.StringReader(value)) {
|
using ( var sr = new System.IO.StringReader(_terms) ) {
|
||||||
var stream = _analyzer.TokenStream(field, sr);
|
var stream = _analyzer.TokenStream(_field, sr);
|
||||||
while(stream.IncrementToken()) {
|
|
||||||
tokens.Add(((TermAttribute)stream.GetAttribute(typeof(TermAttribute))).Term());
|
if(_stem) {
|
||||||
|
stream = new PorterStemFilter(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
while ( stream.IncrementToken() ) {
|
||||||
|
tokens.Add(( (TermAttribute)stream.GetAttribute(typeof(TermAttribute)) ).Term());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_fields[field] = tokens
|
var clauses = tokens
|
||||||
.Where(k => !String.IsNullOrWhiteSpace(k))
|
.Where(k => !String.IsNullOrWhiteSpace(k)) // remove empty strings
|
||||||
.Select(QueryParser.Escape)
|
.Select(QueryParser.Escape) // escape special chars (e.g. C#)
|
||||||
.Select(k => wildcardSearch ? (Query)new PrefixQuery(new Term(field, k)) : new TermQuery(new Term(k)))
|
.Select(k => new Term(_field, k)) // creates the Term instance
|
||||||
.ToArray();
|
.Select(t => _prefix ? new PrefixQuery(t) as Query : new TermQuery(t) as Query) // apply the corresponding Query
|
||||||
|
.Select(q => {
|
||||||
return this;
|
if (_boost != 0) q.SetBoost(_boost);
|
||||||
|
return q;
|
||||||
|
})
|
||||||
|
.Select(q => new BooleanClause(q, _occur)); // apply the corresponding clause
|
||||||
|
|
||||||
|
if ( !_fields.ContainsKey(_field) ) {
|
||||||
|
_fields[_field] = new BooleanClause[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
_fields[_field] = _fields[_field].Union(clauses).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ISearchBuilder After(string name, DateTime date) {
|
public ISearchBuilder After(string name, DateTime date) {
|
||||||
@@ -120,16 +182,23 @@ namespace Orchard.Core.Indexing.Lucene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Query CreateQuery() {
|
private Query CreateQuery() {
|
||||||
if(!String.IsNullOrWhiteSpace(_parse)) {
|
CreatePendingClause();
|
||||||
return new QueryParser(DefaultIndexProvider.LuceneVersion, _defaultField, DefaultIndexProvider.CreateAnalyzer()).Parse(_parse);
|
|
||||||
}
|
|
||||||
|
|
||||||
var query = new BooleanQuery();
|
var query = new BooleanQuery();
|
||||||
|
|
||||||
|
if(!String.IsNullOrWhiteSpace(_parse)) {
|
||||||
|
|
||||||
|
foreach ( var defaultField in _defaultFields ) {
|
||||||
|
var clause = new BooleanClause(new QueryParser(DefaultIndexProvider.LuceneVersion, defaultField, DefaultIndexProvider.CreateAnalyzer()).Parse(_parse), BooleanClause.Occur.SHOULD);
|
||||||
|
query.Add(clause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if ( _fields.Keys.Count > 0 ) { // apply specific filters if defined
|
if ( _fields.Keys.Count > 0 ) { // apply specific filters if defined
|
||||||
foreach ( var filters in _fields.Values ) {
|
foreach ( var clauses in _fields.Values ) {
|
||||||
foreach(var filter in filters)
|
foreach( var clause in clauses)
|
||||||
query.Add(filter, BooleanClause.Occur.SHOULD);
|
query.Add(clause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,11 +37,10 @@ namespace Orchard.Search.Services
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
var searchBuilder = _indexManager.GetSearchIndexProvider().CreateSearchBuilder(SearchIndexName)
|
var searchBuilder = _indexManager.GetSearchIndexProvider().CreateSearchBuilder(SearchIndexName)
|
||||||
.WithField("title", query)
|
.Parse(new [] {"title", "body"}, query);
|
||||||
.WithField("body", query);
|
|
||||||
|
|
||||||
if(HttpContext.Current != null) {
|
if(HttpContext.Current != null) {
|
||||||
searchBuilder.WithField("culture", _cultureManager.GetCurrentCulture(HttpContext.Current));
|
searchBuilder.WithField("culture", _cultureManager.GetCurrentCulture(HttpContext.Current)).Mandatory();
|
||||||
}
|
}
|
||||||
|
|
||||||
var totalCount = searchBuilder.Count();
|
var totalCount = searchBuilder.Count();
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace Orchard.Indexing {
|
namespace Orchard.Indexing {
|
||||||
public interface ISearchBuilder {
|
public interface ISearchBuilder {
|
||||||
|
ISearchBuilder Parse(string[] defaultFields, string query);
|
||||||
ISearchBuilder Parse(string defaultField, string query);
|
|
||||||
|
|
||||||
ISearchBuilder WithField(string field, string value);
|
ISearchBuilder WithField(string field, string value);
|
||||||
ISearchBuilder WithField(string field, string value, bool wildcardSearch);
|
ISearchBuilder Mandatory();
|
||||||
|
ISearchBuilder Forbidden();
|
||||||
|
ISearchBuilder ExactMatch();
|
||||||
|
ISearchBuilder Weighted(float weight);
|
||||||
|
|
||||||
ISearchBuilder After(string name, DateTime date);
|
ISearchBuilder After(string name, DateTime date);
|
||||||
ISearchBuilder Before(string name, DateTime date);
|
ISearchBuilder Before(string name, DateTime date);
|
||||||
|
|||||||
Reference in New Issue
Block a user