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

V4.0 Facet Drill Down and Drill Sideways support #364

Open
wants to merge 15 commits into
base: release/4.0
Choose a base branch
from
111 changes: 111 additions & 0 deletions docs/articles/searching.md
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,117 @@ var createdFacetResults = results.GetFacet("Created"); // Returns the facets for
var firstRangeValue = createdFacetResults.Facet("first"); // Gets the IFacetValue for the facet "first"
```

### DrillDown Query

DrillDown query is used to drill down and sideways on facets.

#### Basic MatchAll Query example

```csharp
// Setup

// Create a config
var facetsConfig = new FacetsConfig();
facetConfigs.SetHierarchical("publishDate", true);

services.AddExamineLuceneIndex("MyIndex",
// Set the indexing of your fields to use the facet type
fieldDefinitions: new FieldDefinitionCollection(
new FieldDefinition("publishDate", FieldDefinitionTypes.FacetTaxonomyFullText),
new FieldDefinition("Author", FieldDefinitionTypes.FacetTaxonomyFullText)
),
// Pass your config
facetsConfig: facetsConfig,
// Enable the Taxonomy sidecar index, required for Heirachy facets
useTaxonomyIndex: true
);


var searcher = myIndex.Searcher;
var results = searcher.CreateQuery()
.DrillDownQuery(
dims =>
{
// Specify the dimensions to drill down on
dims.AddDimension("Author", "Lisa");
},
null // Optional base query to drill down over
,
sideways =>
{
// Drill Sideways for the top 10 facets
sideways.SetTopN(10);
},
BooleanOperation.Or // Default operation for boolean subqueries
)
.WithFacets((Action<IFacetOperations>)(facets =>
{
//Fetch the facets
facets.FacetString("publishDate", x => x.MaxCount(10));
facets.FacetString("Author", x => x.MaxCount(10));
}));
.Execute();

var publishDateResults = results.GetFacet("publishDate"); // Returns the facets for the specific field publishDate
var AuthorFacetResults = results.GetFacet("Author"); // Returns the facets for the specific field publishDate

```

#### Basic BaseQuery example

```csharp
// Setup

// Create a config
var facetsConfig = new FacetsConfig();
facetConfigs.SetHierarchical("publishDate", true);

services.AddExamineLuceneIndex("MyIndex",
// Set the indexing of your fields to use the facet type
fieldDefinitions: new FieldDefinitionCollection(
new FieldDefinition("publishDate", FieldDefinitionTypes.FacetTaxonomyFullText),
new FieldDefinition("Author", FieldDefinitionTypes.FacetTaxonomyFullText)
),
// Pass your config
facetsConfig: facetsConfig,
// Enable the Taxonomy sidecar index, required for Heirachy facets
useTaxonomyIndex: true
);


var searcher = myIndex.Searcher;
var results = searcher.CreateQuery()
.DrillDownQuery(
dims =>
{
// Specify the dimensions to drill down on
dims.AddDimension("Author", "Lisa");
},
baseQuery => // Optional base query to drill down over
{
return baseQuery.Field(ExamineFieldNames.CategoryFieldName, "content");
}
,
sideways =>
{
// Drill Sideways for the top 10 facets
sideways.SetTopN(10);
},
BooleanOperation.Or // Default operation for boolean subqueries
)
.WithFacets((Action<IFacetOperations>)(facets =>
{
//Fetch the facets
facets.FacetString("publishDate", x => x.MaxCount(10));
facets.FacetString("Author", x => x.MaxCount(10));
}));
.Execute();

var publishDateResults = results.GetFacet("publishDate"); // Returns the facets for the specific field publishDate
var AuthorFacetResults = results.GetFacet("Author"); // Returns the facets for the specific field publishDate

```

## Lucene queries

Find a reference to how to write Lucene queries in the [Lucene 4.8.0 docs](https://lucene.apache.org/core/4_8_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#package_description).
Expand Down
18 changes: 18 additions & 0 deletions src/Examine.Core/Search/IDrillDownQueryDimensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Examine.Search
{
/// <summary>
/// Drill-down Query Dimensions
/// </summary>
public interface IDrillDownQueryDimensions

Check warning on line 6 in src/Examine.Core/Search/IDrillDownQueryDimensions.cs

View workflow job for this annotation

GitHub Actions / build

Symbol 'IDrillDownQueryDimensions' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 6 in src/Examine.Core/Search/IDrillDownQueryDimensions.cs

View workflow job for this annotation

GitHub Actions / build

Symbol 'IDrillDownQueryDimensions' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)
{
/// <summary>
/// Adds one dimension of drill-downs.
/// Repeated dimensions are OR'd with the previous contraints for the dimension.
/// All demensions are AND'd againt each other and the base query.
/// </summary>
/// <param name="dimensionName">Dimension Name</param>
/// <param name="paths">Facet Category Paths</param>
/// <returns></returns>
IDrillDownQueryDimensions AddDimension(string dimensionName, params string[] paths);

Check warning on line 16 in src/Examine.Core/Search/IDrillDownQueryDimensions.cs

View workflow job for this annotation

GitHub Actions / build

Check warning on line 16 in src/Examine.Core/Search/IDrillDownQueryDimensions.cs

View workflow job for this annotation

GitHub Actions / build

}
}
15 changes: 15 additions & 0 deletions src/Examine.Core/Search/IDrillSideways.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Examine.Search
{
/// <summary>
/// Drill Sideways Options
/// </summary>
public interface IDrillSideways

Check warning on line 6 in src/Examine.Core/Search/IDrillSideways.cs

View workflow job for this annotation

GitHub Actions / build

Check warning on line 6 in src/Examine.Core/Search/IDrillSideways.cs

View workflow job for this annotation

GitHub Actions / build

{
/// <summary>
/// Set the number of Top Documents
/// </summary>
/// <param name="topN">Number of Top Documents</param>
/// <returns></returns>
IDrillSideways SetTopN(int topN);

Check warning on line 13 in src/Examine.Core/Search/IDrillSideways.cs

View workflow job for this annotation

GitHub Actions / build

Check warning on line 13 in src/Examine.Core/Search/IDrillSideways.cs

View workflow job for this annotation

GitHub Actions / build

}
}
7 changes: 7 additions & 0 deletions src/Examine.Core/Search/IFacetOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,12 @@
/// Add a range facet to the current query
/// </summary>
IFacetOperations FacetLongRange(string field, params Int64Range[] longRanges);

/// <summary>
/// Return all Facet Dimensions that had hits
/// </summary>
/// <param name="maxCount">Maximum number of terms to return</param>
/// <returns></returns>
IFacetOperations FacetAllDimensions(int maxCount);

Check warning on line 40 in src/Examine.Core/Search/IFacetOperations.cs

View workflow job for this annotation

GitHub Actions / build

Symbol 'FacetAllDimensions' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)

Check warning on line 40 in src/Examine.Core/Search/IFacetOperations.cs

View workflow job for this annotation

GitHub Actions / build

Symbol 'FacetAllDimensions' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md)
}
}
9 changes: 8 additions & 1 deletion src/Examine.Core/Search/IOrdering.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@
/// Return all fields in the index
/// </summary>
/// <returns></returns>
IOrdering SelectAllFields();
IOrdering SelectAllFields();

/// <summary>
/// Set where to continue searching from
/// </summary>
/// <param name="searchAfter">Search After</param>
/// <returns></returns>
IOrdering SetSearchAfter(SearchAfter searchAfter);

Check warning on line 50 in src/Examine.Core/Search/IOrdering.cs

View workflow job for this annotation

GitHub Actions / build

Check warning on line 50 in src/Examine.Core/Search/IOrdering.cs

View workflow job for this annotation

GitHub Actions / build

}
}
10 changes: 10 additions & 0 deletions src/Examine.Core/Search/IQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,15 @@
/// <param name="maxInclusive"></param>
/// <returns></returns>
IBooleanOperation RangeQuery<T>(string[] fields, T? min, T? max, bool minInclusive = true, bool maxInclusive = true) where T : struct;

/// <summary>
/// Query for drill-down over facet categories. Call dimensions.Add() for each group of categories to drill-down over
/// </summary>
/// <param name="baseQuery">Base Query to Drill Down on</param>
/// <param name="dimensions">Facet Dimensions to Drill Down</param>
/// <param name="drillSideways">Facet Dimensions to Drill Sideways</param>
/// <param name="defaultOp">Base Query default Op</param>
/// <returns></returns>
IOrdering DrillDownQuery(Action<IDrillDownQueryDimensions> dimensions, Func<INestedQuery, INestedBooleanOperation>? baseQuery = null, Action<IDrillSideways>? drillSideways = null, BooleanOperation defaultOp = BooleanOperation.Or);

Check warning on line 146 in src/Examine.Core/Search/IQuery.cs

View workflow job for this annotation

GitHub Actions / build

Check warning on line 146 in src/Examine.Core/Search/IQuery.cs

View workflow job for this annotation

GitHub Actions / build

}
}
22 changes: 22 additions & 0 deletions src/Examine.Core/Search/SearchAfter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Examine.Search
{
/// <summary>
/// Options for Searching After. Used for efficent deep paging.
/// </summary>
public class SearchAfter

Check warning on line 6 in src/Examine.Core/Search/SearchAfter.cs

View workflow job for this annotation

GitHub Actions / build

Check warning on line 6 in src/Examine.Core/Search/SearchAfter.cs

View workflow job for this annotation

GitHub Actions / build

{
/// <summary>
/// Constructor
/// </summary>
/// <param name="searchAfter">String representing the search after value</param>
public SearchAfter(string searchAfter)

Check warning on line 12 in src/Examine.Core/Search/SearchAfter.cs

View workflow job for this annotation

GitHub Actions / build

Check warning on line 12 in src/Examine.Core/Search/SearchAfter.cs

View workflow job for this annotation

GitHub Actions / build

{
Value = searchAfter;
}

/// <summary>
/// String representing the search after value
/// </summary>
public string Value { get; }

Check warning on line 20 in src/Examine.Core/Search/SearchAfter.cs

View workflow job for this annotation

GitHub Actions / build

Check warning on line 20 in src/Examine.Core/Search/SearchAfter.cs

View workflow job for this annotation

GitHub Actions / build

}
}
11 changes: 10 additions & 1 deletion src/Examine.Lucene/Indexing/FullTextType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Lucene.Net.Documents;
using Lucene.Net.Facet;
using Lucene.Net.Facet.SortedSet;
using Lucene.Net.Facet.Taxonomy;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -134,7 +135,15 @@ protected override void AddSingleValue(Document doc, object value)
Field.Store.YES));
}

if (_isFacetable && _taxonomyIndex)
if (_isFacetable && _taxonomyIndex && value is IFacetLabel taxonomyFacetLabel)
{
doc.Add(new FacetField(taxonomyFacetLabel.Components[0], taxonomyFacetLabel.Components.AsSpan().Slice(1).ToArray()));
}
else if (_isFacetable && !_taxonomyIndex && value is IFacetLabel facetLabel)
{
doc.Add(new SortedSetDocValuesFacetField(facetLabel.Components[0], facetLabel.Components[1]));
}
else if(_isFacetable && _taxonomyIndex)
{
doc.Add(new FacetField(FieldName, str));
}
Expand Down
11 changes: 11 additions & 0 deletions src/Examine.Lucene/Search/IFacetExtractionContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Lucene.Net.Facet;
using Lucene.Net.Facet.SortedSet;

namespace Examine.Lucene.Search
{
Expand All @@ -22,6 +23,16 @@ public interface IFacetExtractionContext
/// </summary>
ISearcherReference SearcherReference { get; }

/// <summary>
/// SortedSetReaderState (Non taxonomy indexed)
/// </summary>
SortedSetDocValuesReaderState? SortedSetReaderState { get; }

/// <summary>
/// Drill Sideways Result Facets
/// </summary>
Facets? DrillSidewaysResultFacets { get; }

/// <summary>
/// Get the facet counts for the faceted field
/// </summary>
Expand Down
8 changes: 7 additions & 1 deletion src/Examine.Lucene/Search/LuceneBooleanOperation.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Examine.Lucene.Providers;
using Examine.Search;
using Lucene.Net.Search;

Expand Down Expand Up @@ -82,5 +81,12 @@ public override IQueryExecutor WithFacets(Action<IFacetOperations> facets)
facets.Invoke(luceneFacetOperation);
return luceneFacetOperation;
}

/// <inheritdoc/>
public override IOrdering SetSearchAfter(SearchAfter searchAfter)
{
_search.SetSearchAfter(searchAfter);
return this;
}
}
}
39 changes: 39 additions & 0 deletions src/Examine.Lucene/Search/LuceneBooleanOperationBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,42 @@ protected internal LuceneBooleanOperationBase Op(
return _search.LuceneQuery(_search.Queries.Pop(), outerOp);
}

/// <summary>
/// Used to add a operation
/// </summary>
/// <param name="baseQueryBuilder">Function that the base query will be passed into to create the outer query</param>
/// <param name="inner"></param>
/// <param name="outerOp"></param>
/// <param name="defaultInnerOp"></param>
/// <returns></returns>
protected internal LuceneBooleanOperationBase OpBaseQuery(
Func<Query,Query> baseQueryBuilder,
Func<INestedQuery, INestedBooleanOperation> inner,
BooleanOperation outerOp,
BooleanOperation? defaultInnerOp = null)
{
_search.Queries.Push(new BooleanQuery());

//change the default inner op if specified
var currentOp = _search.BooleanOperation;
if (defaultInnerOp != null)
{
_search.BooleanOperation = defaultInnerOp.Value;
}

//run the inner search
inner(_search);

//reset to original op if specified
if (defaultInnerOp != null)
{
_search.BooleanOperation = currentOp;
}
var baseBoolQuery = _search.Queries.Pop();
var baseQuery = baseQueryBuilder(baseBoolQuery);
return _search.LuceneQuery(baseQuery, outerOp);
}

/// <inheritdoc/>
public abstract ISearchResults Execute(QueryOptions? options = null);

Expand All @@ -138,5 +174,8 @@ protected internal LuceneBooleanOperationBase Op(

/// <inheritdoc/>
public abstract IQueryExecutor WithFacets(Action<IFacetOperations> facets);

/// <inheritdoc/>
public abstract IOrdering SetSearchAfter(SearchAfter searchAfter);
}
}
30 changes: 30 additions & 0 deletions src/Examine.Lucene/Search/LuceneDrillDownQueryDimensionBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Lucene.Net.Facet;

namespace Examine.Lucene.Search
{
/// <summary>
/// Lucene DrillDown Query Dimensions base
/// </summary>
public abstract class LuceneDrillDownQueryDimensionBase
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="dimensionName">Dimension Name</param>
public LuceneDrillDownQueryDimensionBase(string dimensionName)
{
DimensionName = dimensionName;
}

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

/// <summary>
/// Add the dimension to the Drill-down Query
/// </summary>
/// <param name="drillDownQuery"></param>
public abstract void Apply(DrillDownQuery drillDownQuery);
}
}
Loading
Loading