diff --git a/src/Orchard.Tests.Modules/Indexing/LuceneSearchBuilderTests.cs b/src/Orchard.Tests.Modules/Indexing/LuceneSearchBuilderTests.cs index 762c1694d..799ab236b 100644 --- a/src/Orchard.Tests.Modules/Indexing/LuceneSearchBuilderTests.cs +++ b/src/Orchard.Tests.Modules/Indexing/LuceneSearchBuilderTests.cs @@ -749,5 +749,73 @@ namespace Orchard.Tests.Modules.Indexing { Assert.That(SearchBuilder.WithinRange("number", null, 123.456, true, false).Count(), Is.EqualTo(0)); Assert.That(SearchBuilder.WithinRange("integer", null, 123, true, false).Count(), Is.EqualTo(0)); } + + [Test] + public void ShouldAllowGroupedClauses() { + _provider.CreateIndex("default"); + _provider.Store("default", _provider.New(1).Add("body", "michael is in the kitchen").Analyze()); + _provider.Store("default", _provider.New(2).Add("body", "michael has a cousin named michel").Analyze()); + _provider.Store("default", _provider.New(3).Add("body", "speak inside the mic").Analyze()); + _provider.Store("default", _provider.New(4).Add("body", "a dog is pursuing a cat").Analyze()); + _provider.Store("default", _provider.New(5).Add("body", "michael speaks to elephants").Analyze()); + + var michael = SearchBuilder.WithField("body", "michael").GetBits(); + var cousinKitchenOrCat = SearchBuilder.WithField("body", "cousin").WithField("body", "kitchen").WithField("body", "cat").GetBits(); + + Assert.That(michael.Count(), Is.EqualTo(3)); + Assert.That(cousinKitchenOrCat.Count(), Is.EqualTo(3)); + + // Contains "michael" AND ("cousin" OR "kitchen" OR "cat") + var michaelAndCousinKitchenOrcat = SearchBuilder + .WithField("body", "michael").Mandatory() + .WithGroup(groupSearchBuilder => groupSearchBuilder + .WithField("body", "kitchen") + .WithField("body", "cousin") + .WithField("body", "cat")).Mandatory().GetBits(); + + Assert.That(michaelAndCousinKitchenOrcat.Count(), Is.EqualTo(2)); + Assert.That(michael.And(cousinKitchenOrCat).Count(), Is.EqualTo(2)); + } + + [Test] + public void ShouldAllowNestedGroupedClauses() + { + _provider.CreateIndex("default"); + _provider.Store("default", _provider.New(1).Add("body", "michael is in the kitchen with his cousin").Analyze()); + _provider.Store("default", _provider.New(2).Add("body", "michael has a cousin named michel").Analyze()); + _provider.Store("default", _provider.New(3).Add("body", "speak inside the mic").Analyze()); + _provider.Store("default", _provider.New(4).Add("body", "a dog is pursuing a cat").Analyze()); + _provider.Store("default", _provider.New(5).Add("body", "michael speaks to elephants").Analyze()); + + var michael = SearchBuilder.WithField("body", "michael").GetBits(); + var elephant = SearchBuilder.WithField("body", "elephant").GetBits(); + var cousinAndKitchen = SearchBuilder + .WithField("body", "cousin").Mandatory() + .WithField("body", "kitchen").Mandatory().GetBits(); + + // Contains "elephant" OR ("cousin" AND "kitchen") + var elephantOrCousinAndKitchen = SearchBuilder + .WithField("body", "elephant") + .WithGroup(nestedSearchBuilder => nestedSearchBuilder + .WithField("body", "cousin").Mandatory() + .WithField("body", "kitchen").Mandatory() + ).GetBits(); + + // Contains "michael" AND ("elephant" OR ("cousin" AND "kitchen")) + var michaelAndElephantOrCousinAndKitchen = SearchBuilder + .WithField("body", "michael").Mandatory() + .WithGroup(groupSearchBuilder => groupSearchBuilder + .WithField("body", "elephant") + .WithGroup(nestedSearchBuilder => nestedSearchBuilder + .WithField("body", "cousin").Mandatory() + .WithField("body", "kitchen").Mandatory()) + ).Mandatory().GetBits(); + + Assert.That(michael.Count(), Is.EqualTo(3)); + Assert.That(elephant.Count(), Is.EqualTo(1)); + Assert.That(cousinAndKitchen.Count(), Is.EqualTo(1)); + Assert.That(elephantOrCousinAndKitchen.Count(), Is.EqualTo(2)); + Assert.That(michaelAndElephantOrCousinAndKitchen.Count(), Is.EqualTo(2)); + } } } diff --git a/src/Orchard.Web/Modules/Lucene/Services/LuceneSearchBuilder.cs b/src/Orchard.Web/Modules/Lucene/Services/LuceneSearchBuilder.cs index 213cd2781..81630202a 100644 --- a/src/Orchard.Web/Modules/Lucene/Services/LuceneSearchBuilder.cs +++ b/src/Orchard.Web/Modules/Lucene/Services/LuceneSearchBuilder.cs @@ -22,6 +22,7 @@ namespace Lucene.Services { private readonly Directory _directory; private string _indexName; private Analyzer _analyzer; + private ILuceneAnalyzerProvider _analyzerProvider; private readonly List _clauses; private readonly List _filters; @@ -47,8 +48,10 @@ namespace Lucene.Services { string indexName) { _directory = directory; _indexName = indexName; + _analyzerProvider = analyzerProvider; _analyzer = analyzerProvider.GetAnalyzer(_indexName); + Logger = NullLogger.Instance; _count = MaxResults; @@ -87,6 +90,18 @@ namespace Lucene.Services { return this; } + public ISearchBuilder WithGroup(Action grouping) + { + CreatePendingClause(); + + // a group is essentially a SearchBuilder + var groupSearchBuilder = new LuceneSearchBuilder(null, _analyzerProvider, _indexName); + grouping(groupSearchBuilder); + + _query = groupSearchBuilder.CreateQuery(); + return this; + } + public ISearchBuilder WithField(string field, int value) { CreatePendingClause(); _query = NumericRangeQuery.NewIntRange(field, value, value, true, true); diff --git a/src/Orchard/Indexing/ISearchBuilder.cs b/src/Orchard/Indexing/ISearchBuilder.cs index e99f35300..9621b1307 100644 --- a/src/Orchard/Indexing/ISearchBuilder.cs +++ b/src/Orchard/Indexing/ISearchBuilder.cs @@ -6,6 +6,7 @@ namespace Orchard.Indexing { ISearchBuilder Parse(string defaultField, string query, bool escape = true); ISearchBuilder Parse(string[] defaultFields, string query, bool escape = true); + ISearchBuilder WithGroup(Action groupSearchBuilder); ISearchBuilder WithField(string field, bool value); ISearchBuilder WithField(string field, DateTime value); ISearchBuilder WithField(string field, string value); diff --git a/src/Orchard/Indexing/NullSearchBuilder.cs b/src/Orchard/Indexing/NullSearchBuilder.cs index 807be880a..686662083 100644 --- a/src/Orchard/Indexing/NullSearchBuilder.cs +++ b/src/Orchard/Indexing/NullSearchBuilder.cs @@ -12,6 +12,10 @@ namespace Orchard.Indexing { return this; } + public ISearchBuilder WithGroup(Action groupSearchBuilder) { + return this; + } + public ISearchBuilder WithField(string field, bool value) { return this; }