2010-06-02 15:56:54 -07:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
using System.Linq;
|
2010-07-09 13:16:02 -07:00
|
|
|
|
using Lucene.Models;
|
2010-06-02 15:56:54 -07:00
|
|
|
|
using Lucene.Net.Index;
|
|
|
|
|
using Lucene.Net.Search;
|
|
|
|
|
using Lucene.Net.Store;
|
2010-07-09 13:16:02 -07:00
|
|
|
|
using Orchard.Indexing;
|
2010-06-02 15:56:54 -07:00
|
|
|
|
using Orchard.Logging;
|
|
|
|
|
using Lucene.Net.Documents;
|
2010-06-07 12:26:32 -07:00
|
|
|
|
using Lucene.Net.QueryParsers;
|
2010-06-02 15:56:54 -07:00
|
|
|
|
|
2010-07-09 13:16:02 -07:00
|
|
|
|
namespace Lucene.Services {
|
2010-06-17 16:21:29 -07:00
|
|
|
|
public class LuceneSearchBuilder : ISearchBuilder {
|
2010-06-02 15:56:54 -07:00
|
|
|
|
|
|
|
|
|
private const int MaxResults = Int16.MaxValue;
|
|
|
|
|
|
|
|
|
|
private readonly Directory _directory;
|
|
|
|
|
|
2010-06-17 16:21:29 -07:00
|
|
|
|
private readonly List<BooleanClause> _clauses;
|
2010-06-18 12:22:06 -07:00
|
|
|
|
private readonly List<BooleanClause> _filters;
|
2010-06-02 15:56:54 -07:00
|
|
|
|
private int _count;
|
|
|
|
|
private int _skip;
|
|
|
|
|
private string _sort;
|
2011-01-31 17:30:45 -08:00
|
|
|
|
private int _comparer;
|
2010-06-02 15:56:54 -07:00
|
|
|
|
private bool _sortDescending;
|
2010-06-18 12:22:06 -07:00
|
|
|
|
private bool _asFilter;
|
2010-06-16 14:00:08 -07:00
|
|
|
|
|
|
|
|
|
// pending clause attributes
|
|
|
|
|
private BooleanClause.Occur _occur;
|
2010-06-17 16:21:29 -07:00
|
|
|
|
private bool _exactMatch;
|
2010-06-16 14:00:08 -07:00
|
|
|
|
private float _boost;
|
2010-06-17 16:21:29 -07:00
|
|
|
|
private Query _query;
|
2010-06-02 15:56:54 -07:00
|
|
|
|
|
|
|
|
|
public ILogger Logger { get; set; }
|
|
|
|
|
|
2010-06-17 16:21:29 -07:00
|
|
|
|
public LuceneSearchBuilder(Directory directory) {
|
2010-06-02 15:56:54 -07:00
|
|
|
|
_directory = directory;
|
|
|
|
|
Logger = NullLogger.Instance;
|
|
|
|
|
|
|
|
|
|
_count = MaxResults;
|
|
|
|
|
_skip = 0;
|
2010-06-17 16:21:29 -07:00
|
|
|
|
_clauses = new List<BooleanClause>();
|
2010-06-18 12:22:06 -07:00
|
|
|
|
_filters = new List<BooleanClause>();
|
2010-06-02 15:56:54 -07:00
|
|
|
|
_sort = String.Empty;
|
2011-01-31 17:30:45 -08:00
|
|
|
|
_comparer = 0;
|
2010-06-02 15:56:54 -07:00
|
|
|
|
_sortDescending = true;
|
2010-06-16 14:00:08 -07:00
|
|
|
|
|
|
|
|
|
InitPendingClause();
|
2010-06-02 15:56:54 -07:00
|
|
|
|
}
|
2010-07-29 15:44:15 -07:00
|
|
|
|
|
2011-01-11 14:06:15 -08:00
|
|
|
|
public ISearchBuilder Parse(string defaultField, string query, bool escape, bool mandatory) {
|
|
|
|
|
return Parse(new[] {defaultField}, query, escape, mandatory);
|
2010-06-17 16:21:29 -07:00
|
|
|
|
}
|
|
|
|
|
|
2011-01-11 14:06:15 -08:00
|
|
|
|
public ISearchBuilder Parse(string[] defaultFields, string query, bool escape, bool mandatory) {
|
2010-06-16 14:00:08 -07:00
|
|
|
|
if ( defaultFields.Length == 0 ) {
|
2010-06-07 12:33:19 -07:00
|
|
|
|
throw new ArgumentException("Default field can't be empty");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( String.IsNullOrWhiteSpace(query) ) {
|
|
|
|
|
throw new ArgumentException("Query can't be empty");
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-11 14:06:15 -08:00
|
|
|
|
if (escape) {
|
|
|
|
|
query = QueryParser.Escape(query);
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-18 12:22:06 -07:00
|
|
|
|
var analyzer = LuceneIndexProvider.CreateAnalyzer();
|
|
|
|
|
foreach ( var defaultField in defaultFields ) {
|
2011-01-11 14:06:15 -08:00
|
|
|
|
var clause = new BooleanClause(new QueryParser(LuceneIndexProvider.LuceneVersion, defaultField, analyzer).Parse(query), mandatory ? BooleanClause.Occur.MUST : BooleanClause.Occur.SHOULD);
|
2010-06-18 12:22:06 -07:00
|
|
|
|
_clauses.Add(clause);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_query = null;
|
2010-06-02 15:56:54 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-17 16:21:29 -07:00
|
|
|
|
public ISearchBuilder WithField(string field, int value) {
|
2010-06-16 14:00:08 -07:00
|
|
|
|
CreatePendingClause();
|
2010-06-17 16:21:29 -07:00
|
|
|
|
_query = NumericRangeQuery.NewIntRange(field, value, value, true, true);
|
|
|
|
|
return this;
|
|
|
|
|
}
|
2010-06-16 14:00:08 -07:00
|
|
|
|
|
2010-06-17 16:21:29 -07:00
|
|
|
|
public ISearchBuilder WithinRange(string field, int min, int max) {
|
|
|
|
|
CreatePendingClause();
|
|
|
|
|
_query = NumericRangeQuery.NewIntRange(field, min, max, true, true);
|
|
|
|
|
return this;
|
|
|
|
|
}
|
2010-06-16 14:00:08 -07:00
|
|
|
|
|
2010-06-17 16:21:29 -07:00
|
|
|
|
public ISearchBuilder WithField(string field, float value) {
|
|
|
|
|
CreatePendingClause();
|
|
|
|
|
_query = NumericRangeQuery.NewFloatRange(field, value, value, true, true);
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ISearchBuilder WithinRange(string field, float min, float max) {
|
|
|
|
|
CreatePendingClause();
|
|
|
|
|
_query = NumericRangeQuery.NewFloatRange(field, min, max, true, true);
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ISearchBuilder WithField(string field, bool value) {
|
|
|
|
|
CreatePendingClause();
|
|
|
|
|
_query = new TermQuery(new Term(field, value.ToString()));
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ISearchBuilder WithField(string field, DateTime value) {
|
|
|
|
|
CreatePendingClause();
|
2011-01-31 17:30:45 -08:00
|
|
|
|
_query = new TermQuery(new Term(field, DateTools.DateToString(value, DateTools.Resolution.MILLISECOND)));
|
2010-06-17 16:21:29 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ISearchBuilder WithinRange(string field, DateTime min, DateTime max) {
|
|
|
|
|
CreatePendingClause();
|
2011-01-31 17:30:45 -08:00
|
|
|
|
_query = new TermRangeQuery(field, DateTools.DateToString(min, DateTools.Resolution.MILLISECOND), DateTools.DateToString(max, DateTools.Resolution.MILLISECOND), true, true);
|
2010-06-17 16:21:29 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ISearchBuilder WithinRange(string field, string min, string max) {
|
|
|
|
|
CreatePendingClause();
|
|
|
|
|
_query = new TermRangeQuery(field, QueryParser.Escape(min.ToLower()), QueryParser.Escape(min.ToLower()), true, true);
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ISearchBuilder WithField(string field, string value) {
|
|
|
|
|
CreatePendingClause();
|
|
|
|
|
|
|
|
|
|
if ( !String.IsNullOrWhiteSpace(value) ) {
|
|
|
|
|
_query = new TermQuery(new Term(field, QueryParser.Escape(value.ToLower())));
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-16 14:00:08 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ISearchBuilder Mandatory() {
|
|
|
|
|
_occur = BooleanClause.Occur.MUST;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ISearchBuilder Forbidden() {
|
|
|
|
|
_occur = BooleanClause.Occur.MUST_NOT;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ISearchBuilder ExactMatch() {
|
2010-06-17 16:21:29 -07:00
|
|
|
|
_exactMatch = true;
|
2010-06-16 14:00:08 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ISearchBuilder Weighted(float weight) {
|
|
|
|
|
_boost = weight;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void InitPendingClause() {
|
|
|
|
|
_occur = BooleanClause.Occur.SHOULD;
|
2010-06-17 16:21:29 -07:00
|
|
|
|
_exactMatch = false;
|
|
|
|
|
_query = null;
|
2010-06-16 14:00:08 -07:00
|
|
|
|
_boost = 0;
|
2010-06-18 12:22:06 -07:00
|
|
|
|
_asFilter = false;
|
2011-01-31 17:30:45 -08:00
|
|
|
|
_sort = String.Empty;
|
|
|
|
|
_comparer = 0;
|
2010-06-02 15:56:54 -07:00
|
|
|
|
}
|
|
|
|
|
|
2010-06-16 14:00:08 -07:00
|
|
|
|
private void CreatePendingClause() {
|
2010-06-17 16:21:29 -07:00
|
|
|
|
if(_query == null) {
|
2010-06-16 14:00:08 -07:00
|
|
|
|
return;
|
|
|
|
|
}
|
2010-06-02 15:56:54 -07:00
|
|
|
|
|
2010-06-17 16:21:29 -07:00
|
|
|
|
if (_boost != 0) {
|
|
|
|
|
_query.SetBoost(_boost);
|
2010-06-07 12:26:32 -07:00
|
|
|
|
}
|
|
|
|
|
|
2010-06-17 16:21:29 -07:00
|
|
|
|
if(!_exactMatch) {
|
|
|
|
|
var termQuery = _query as TermQuery;
|
|
|
|
|
if(termQuery != null) {
|
|
|
|
|
_query = new PrefixQuery(termQuery.GetTerm());
|
|
|
|
|
}
|
2010-06-16 14:00:08 -07:00
|
|
|
|
}
|
2010-06-18 12:22:06 -07:00
|
|
|
|
if ( _asFilter ) {
|
|
|
|
|
_filters.Add(new BooleanClause(_query, _occur));
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
_clauses.Add(new BooleanClause(_query, _occur));
|
|
|
|
|
}
|
2010-06-02 15:56:54 -07:00
|
|
|
|
}
|
|
|
|
|
|
2011-01-31 17:30:45 -08:00
|
|
|
|
public ISearchBuilder SortBy(string name)
|
|
|
|
|
{
|
|
|
|
|
_sort = name;
|
|
|
|
|
_comparer = 0;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ISearchBuilder SortByInteger(string name) {
|
|
|
|
|
_sort = name;
|
|
|
|
|
_comparer = SortField.INT;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ISearchBuilder SortByString(string name) {
|
|
|
|
|
_sort = name;
|
|
|
|
|
_comparer = SortField.STRING;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ISearchBuilder SortByFloat(string name) {
|
|
|
|
|
_sort = name;
|
|
|
|
|
_comparer = SortField.FLOAT;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ISearchBuilder SortByDateTime(string name)
|
|
|
|
|
{
|
2010-06-02 15:56:54 -07:00
|
|
|
|
_sort = name;
|
2011-01-31 17:30:45 -08:00
|
|
|
|
_comparer = SortField.LONG;
|
2010-06-02 15:56:54 -07:00
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2011-01-31 17:30:45 -08:00
|
|
|
|
public ISearchBuilder Ascending()
|
|
|
|
|
{
|
2010-06-02 15:56:54 -07:00
|
|
|
|
_sortDescending = false;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-18 12:22:06 -07:00
|
|
|
|
public ISearchBuilder AsFilter() {
|
|
|
|
|
_asFilter = true;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
2010-06-02 15:56:54 -07:00
|
|
|
|
public ISearchBuilder Slice(int skip, int count) {
|
|
|
|
|
if ( skip < 0 ) {
|
|
|
|
|
throw new ArgumentException("Skip must be greater or equal to zero");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( count <= 0 ) {
|
|
|
|
|
throw new ArgumentException("Count must be greater than zero");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_skip = skip;
|
|
|
|
|
_count = count;
|
|
|
|
|
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Query CreateQuery() {
|
2010-06-16 14:00:08 -07:00
|
|
|
|
CreatePendingClause();
|
|
|
|
|
|
2011-01-11 17:50:19 -08:00
|
|
|
|
var booleanQuery = new BooleanQuery();
|
|
|
|
|
Query resultQuery = booleanQuery;
|
2010-06-16 14:00:08 -07:00
|
|
|
|
|
2011-01-11 17:50:19 -08:00
|
|
|
|
if (_clauses.Count == 0) {
|
|
|
|
|
if (_filters.Count > 0) { // only filters applieds => transform to a boolean query
|
|
|
|
|
foreach (var clause in _filters) {
|
|
|
|
|
booleanQuery.Add(clause);
|
|
|
|
|
}
|
2010-06-02 15:56:54 -07:00
|
|
|
|
|
2011-01-11 17:50:19 -08:00
|
|
|
|
resultQuery = booleanQuery;
|
|
|
|
|
}
|
|
|
|
|
else { // search all documents, without filter or clause
|
|
|
|
|
resultQuery = new MatchAllDocsQuery(null);
|
|
|
|
|
}
|
2010-06-02 15:56:54 -07:00
|
|
|
|
}
|
2011-01-11 17:50:19 -08:00
|
|
|
|
else {
|
|
|
|
|
foreach (var clause in _clauses)
|
|
|
|
|
booleanQuery.Add(clause);
|
2010-06-02 15:56:54 -07:00
|
|
|
|
|
2011-01-11 17:50:19 -08:00
|
|
|
|
if (_filters.Count > 0) {
|
|
|
|
|
var filter = new BooleanQuery();
|
|
|
|
|
foreach (var clause in _filters)
|
|
|
|
|
filter.Add(clause);
|
|
|
|
|
var queryFilter = new QueryWrapperFilter(filter);
|
2010-06-18 12:22:06 -07:00
|
|
|
|
|
2011-01-11 17:50:19 -08:00
|
|
|
|
resultQuery = new FilteredQuery(booleanQuery, queryFilter);
|
|
|
|
|
}
|
2010-06-18 12:22:06 -07:00
|
|
|
|
}
|
|
|
|
|
|
2011-01-11 17:50:19 -08:00
|
|
|
|
Logger.Debug("New search query: {0}", resultQuery.ToString());
|
|
|
|
|
return resultQuery;
|
2010-06-02 15:56:54 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IEnumerable<ISearchHit> Search() {
|
|
|
|
|
var query = CreateQuery();
|
2010-06-17 16:21:29 -07:00
|
|
|
|
|
2010-06-08 11:51:37 -07:00
|
|
|
|
IndexSearcher searcher;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
searcher = new IndexSearcher(_directory, true);
|
|
|
|
|
}
|
|
|
|
|
catch {
|
|
|
|
|
// index might not exist if it has been rebuilt
|
|
|
|
|
Logger.Information("Attempt to read a none existing index");
|
|
|
|
|
return Enumerable.Empty<ISearchHit>();
|
|
|
|
|
}
|
2010-06-02 15:56:54 -07:00
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
var sort = String.IsNullOrEmpty(_sort)
|
|
|
|
|
? Sort.RELEVANCE
|
2011-01-31 17:30:45 -08:00
|
|
|
|
: new Sort(new SortField(_sort, _comparer, _sortDescending));
|
2010-06-02 15:56:54 -07:00
|
|
|
|
var collector = TopFieldCollector.create(
|
|
|
|
|
sort,
|
|
|
|
|
_count + _skip,
|
|
|
|
|
false,
|
|
|
|
|
true,
|
|
|
|
|
false,
|
|
|
|
|
true);
|
|
|
|
|
|
2010-06-17 16:21:29 -07:00
|
|
|
|
Logger.Debug("Searching: {0}", query.ToString());
|
2010-06-02 15:56:54 -07:00
|
|
|
|
searcher.Search(query, collector);
|
|
|
|
|
|
2010-06-17 16:21:29 -07:00
|
|
|
|
var results = collector.TopDocs().scoreDocs
|
|
|
|
|
.Skip(_skip)
|
|
|
|
|
.Select(scoreDoc => new LuceneSearchHit(searcher.Doc(scoreDoc.doc), scoreDoc.score))
|
|
|
|
|
.ToList();
|
2010-06-02 15:56:54 -07:00
|
|
|
|
|
2010-06-17 16:21:29 -07:00
|
|
|
|
Logger.Debug("Search results: {0}", results.Count);
|
2010-06-02 15:56:54 -07:00
|
|
|
|
|
|
|
|
|
return results;
|
|
|
|
|
}
|
|
|
|
|
finally {
|
|
|
|
|
searcher.Close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int Count() {
|
|
|
|
|
var query = CreateQuery();
|
2010-06-08 11:51:37 -07:00
|
|
|
|
IndexSearcher searcher;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
searcher = new IndexSearcher(_directory, true);
|
|
|
|
|
}
|
|
|
|
|
catch {
|
|
|
|
|
// index might not exist if it has been rebuilt
|
|
|
|
|
Logger.Information("Attempt to read a none existing index");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2010-06-02 15:56:54 -07:00
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
var hits = searcher.Search(query, Int16.MaxValue);
|
|
|
|
|
Logger.Information("Search results: {0}", hits.scoreDocs.Length);
|
|
|
|
|
var length = hits.scoreDocs.Length;
|
|
|
|
|
return Math.Min(length - _skip, _count) ;
|
|
|
|
|
}
|
|
|
|
|
finally {
|
|
|
|
|
searcher.Close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ISearchHit Get(int documentId) {
|
|
|
|
|
var query = new TermQuery(new Term("id", documentId.ToString()));
|
|
|
|
|
|
|
|
|
|
var searcher = new IndexSearcher(_directory, true);
|
|
|
|
|
try {
|
|
|
|
|
var hits = searcher.Search(query, 1);
|
|
|
|
|
Logger.Information("Search results: {0}", hits.scoreDocs.Length);
|
2010-12-07 17:42:52 -08:00
|
|
|
|
return hits.scoreDocs.Length > 0 ? new LuceneSearchHit(searcher.Doc(hits.scoreDocs[0].doc), hits.scoreDocs[0].score) : null;
|
2010-06-02 15:56:54 -07:00
|
|
|
|
}
|
|
|
|
|
finally {
|
|
|
|
|
searcher.Close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|