Skip to content
Open
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
{
/// <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);
}
}
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
{
/// <summary>
/// Set the number of Top Documents
/// </summary>
/// <param name="topN">Number of Top Documents</param>
/// <returns></returns>
IDrillSideways SetTopN(int topN);
}
}
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 @@ public interface IFacetOperations : IQueryExecutor
/// 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);
}
}
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 @@ public interface IOrdering : IQueryExecutor
/// 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);
}
}
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 @@ public interface IQuery
/// <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);
}
}
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
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="searchAfter">String representing the search after value</param>
public SearchAfter(string searchAfter)
{
Value = searchAfter;
}

/// <summary>
/// String representing the search after value
/// </summary>
public string Value { get; }
}
}
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