blob: 12b8266f821f8855d268924567b0dcdc83d1803f [file] [log] [blame]
using Lucene.Net.Support;
using System;
using System.Collections.Generic;
using Lucene.Net.Diagnostics;
using System.Text;
namespace Lucene.Net.Search
{
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext;
using IBits = Lucene.Net.Util.IBits;
using IndexReader = Lucene.Net.Index.IndexReader;
using Term = Lucene.Net.Index.Term;
using ToStringUtils = Lucene.Net.Util.ToStringUtils;
/// <summary>
/// A query that wraps another query or a filter and simply returns a constant score equal to the
/// query boost for every document that matches the filter or query.
/// For queries it therefore simply strips of all scores and returns a constant one.
/// </summary>
public class ConstantScoreQuery : Query
{
protected readonly Filter m_filter;
protected readonly Query m_query;
/// <summary>
/// Strips off scores from the passed in <see cref="Search.Query"/>. The hits will get a constant score
/// dependent on the boost factor of this query.
/// </summary>
public ConstantScoreQuery(Query query)
{
this.m_filter = null;
this.m_query = query ?? throw new NullReferenceException("Query may not be null");
}
/// <summary>
/// Wraps a <see cref="Search.Filter"/> as a <see cref="Search.Query"/>. The hits will get a constant score
/// dependent on the boost factor of this query.
/// If you simply want to strip off scores from a <see cref="Search.Query"/>, no longer use
/// <c>new ConstantScoreQuery(new QueryWrapperFilter(query))</c>, instead
/// use <see cref="ConstantScoreQuery(Query)"/>!
/// </summary>
public ConstantScoreQuery(Filter filter)
{
this.m_filter = filter ?? throw new NullReferenceException("Filter may not be null");
this.m_query = null;
}
/// <summary>
/// Returns the encapsulated filter, returns <c>null</c> if a query is wrapped. </summary>
public virtual Filter Filter => m_filter;
/// <summary>
/// Returns the encapsulated query, returns <c>null</c> if a filter is wrapped. </summary>
public virtual Query Query => m_query;
public override Query Rewrite(IndexReader reader)
{
if (m_query != null)
{
Query rewritten = m_query.Rewrite(reader);
if (rewritten != m_query)
{
rewritten = new ConstantScoreQuery(rewritten);
rewritten.Boost = this.Boost;
return rewritten;
}
}
else
{
if (Debugging.AssertsEnabled) Debugging.Assert(m_filter != null);
// Fix outdated usage pattern from Lucene 2.x/early-3.x:
// because ConstantScoreQuery only accepted filters,
// QueryWrapperFilter was used to wrap queries.
if (m_filter is QueryWrapperFilter qwf)
{
Query rewritten = new ConstantScoreQuery(qwf.Query.Rewrite(reader));
rewritten.Boost = this.Boost;
return rewritten;
}
}
return this;
}
public override void ExtractTerms(ISet<Term> terms)
{
// TODO: OK to not add any terms when wrapped a filter
// and used with MultiSearcher, but may not be OK for
// highlighting.
// If a query was wrapped, we delegate to query.
if (m_query != null)
{
m_query.ExtractTerms(terms);
}
}
protected class ConstantWeight : Weight
{
private readonly ConstantScoreQuery outerInstance;
private readonly Weight innerWeight;
private float queryNorm;
private float queryWeight;
public ConstantWeight(ConstantScoreQuery outerInstance, IndexSearcher searcher)
{
this.outerInstance = outerInstance;
this.innerWeight = outerInstance.m_query?.CreateWeight(searcher);
}
public override Query Query => outerInstance;
public override float GetValueForNormalization()
{
// we calculate sumOfSquaredWeights of the inner weight, but ignore it (just to initialize everything)
if (innerWeight != null)
{
innerWeight.GetValueForNormalization();
}
queryWeight = outerInstance.Boost;
return queryWeight * queryWeight;
}
public override void Normalize(float norm, float topLevelBoost)
{
this.queryNorm = norm * topLevelBoost;
queryWeight *= this.queryNorm;
// we normalize the inner weight, but ignore it (just to initialize everything)
if (innerWeight != null)
{
innerWeight.Normalize(norm, topLevelBoost);
}
}
public override BulkScorer GetBulkScorer(AtomicReaderContext context, bool scoreDocsInOrder, IBits acceptDocs)
{
//DocIdSetIterator disi;
if (outerInstance.m_filter != null)
{
if (Debugging.AssertsEnabled) Debugging.Assert(outerInstance.m_query == null);
return base.GetBulkScorer(context, scoreDocsInOrder, acceptDocs);
}
else
{
if (Debugging.AssertsEnabled) Debugging.Assert(outerInstance.m_query != null && innerWeight != null);
BulkScorer bulkScorer = innerWeight.GetBulkScorer(context, scoreDocsInOrder, acceptDocs);
if (bulkScorer == null)
{
return null;
}
return new ConstantBulkScorer(outerInstance, bulkScorer, this, queryWeight);
}
}
public override Scorer GetScorer(AtomicReaderContext context, IBits acceptDocs)
{
DocIdSetIterator disi;
if (outerInstance.m_filter != null)
{
if (Debugging.AssertsEnabled) Debugging.Assert(outerInstance.m_query == null);
DocIdSet dis = outerInstance.m_filter.GetDocIdSet(context, acceptDocs);
if (dis == null)
{
return null;
}
disi = dis.GetIterator();
}
else
{
if (Debugging.AssertsEnabled) Debugging.Assert(outerInstance.m_query != null && innerWeight != null);
disi = innerWeight.GetScorer(context, acceptDocs);
}
if (disi == null)
{
return null;
}
return new ConstantScorer(outerInstance, disi, this, queryWeight);
}
public override bool ScoresDocsOutOfOrder => (innerWeight != null) ? innerWeight.ScoresDocsOutOfOrder : false;
public override Explanation Explain(AtomicReaderContext context, int doc)
{
Scorer cs = GetScorer(context, (context.AtomicReader).LiveDocs);
bool exists = (cs != null && cs.Advance(doc) == doc);
ComplexExplanation result = new ComplexExplanation();
if (exists)
{
result.Description = outerInstance.ToString() + ", product of:";
result.Value = queryWeight;
result.Match = true;
result.AddDetail(new Explanation(outerInstance.Boost, "boost"));
result.AddDetail(new Explanation(queryNorm, "queryNorm"));
}
else
{
result.Description = outerInstance.ToString() + " doesn't match id " + doc;
result.Value = 0;
result.Match = false;
}
return result;
}
}
/// <summary>
/// We return this as our <see cref="BulkScorer"/> so that if the CSQ
/// wraps a query with its own optimized top-level
/// scorer (e.g. <see cref="BooleanScorer"/>) we can use that
/// top-level scorer.
/// </summary>
protected class ConstantBulkScorer : BulkScorer
{
private readonly ConstantScoreQuery outerInstance;
internal readonly BulkScorer bulkScorer;
internal readonly Weight weight;
internal readonly float theScore;
public ConstantBulkScorer(ConstantScoreQuery outerInstance, BulkScorer bulkScorer, Weight weight, float theScore)
{
this.outerInstance = outerInstance;
this.bulkScorer = bulkScorer;
this.weight = weight;
this.theScore = theScore;
}
public override bool Score(ICollector collector, int max)
{
return bulkScorer.Score(WrapCollector(collector), max);
}
private ICollector WrapCollector(ICollector collector)
{
return new CollectorAnonymousClass(this, collector);
}
private class CollectorAnonymousClass : ICollector
{
private readonly ConstantBulkScorer outerInstance;
private readonly ICollector collector;
public CollectorAnonymousClass(ConstantBulkScorer outerInstance, ICollector collector)
{
this.outerInstance = outerInstance;
this.collector = collector;
}
public virtual void SetScorer(Scorer scorer)
{
// we must wrap again here, but using the value passed in as parameter:
collector.SetScorer(new ConstantScorer(outerInstance.outerInstance, scorer, outerInstance.weight, outerInstance.theScore));
}
public virtual void Collect(int doc)
{
collector.Collect(doc);
}
public virtual void SetNextReader(AtomicReaderContext context)
{
collector.SetNextReader(context);
}
public virtual bool AcceptsDocsOutOfOrder => collector.AcceptsDocsOutOfOrder;
}
}
// LUCENENET NOTE: Marked internal for testing
protected internal class ConstantScorer : Scorer
{
private readonly ConstantScoreQuery outerInstance;
internal readonly DocIdSetIterator docIdSetIterator;
internal readonly float theScore;
public ConstantScorer(ConstantScoreQuery outerInstance, DocIdSetIterator docIdSetIterator, Weight w, float theScore)
: base(w)
{
this.outerInstance = outerInstance;
this.theScore = theScore;
this.docIdSetIterator = docIdSetIterator;
}
public override int NextDoc()
{
return docIdSetIterator.NextDoc();
}
public override int DocID => docIdSetIterator.DocID;
public override float GetScore()
{
if (Debugging.AssertsEnabled) Debugging.Assert(docIdSetIterator.DocID != NO_MORE_DOCS);
return theScore;
}
public override int Freq => 1;
public override int Advance(int target)
{
return docIdSetIterator.Advance(target);
}
public override long GetCost()
{
return docIdSetIterator.GetCost();
}
public override ICollection<ChildScorer> GetChildren()
{
if (outerInstance.m_query != null)
{
return new[] { new ChildScorer((Scorer)docIdSetIterator, "constant") };
}
else
{
return Collections.EmptyList<ChildScorer>();
}
}
}
public override Weight CreateWeight(IndexSearcher searcher)
{
return new ConstantScoreQuery.ConstantWeight(this, searcher);
}
public override string ToString(string field)
{
return (new StringBuilder("ConstantScore(")).Append((m_query == null) ? m_filter.ToString() : m_query.ToString(field)).Append(')').Append(ToStringUtils.Boost(Boost)).ToString();
}
public override bool Equals(object o)
{
if (this == o)
{
return true;
}
if (!base.Equals(o))
{
return false;
}
if (o is ConstantScoreQuery other)
{
return ((this.m_filter == null) ? other.m_filter == null : this.m_filter.Equals(other.m_filter)) && ((this.m_query == null) ? other.m_query == null : this.m_query.Equals(other.m_query));
}
return false;
}
public override int GetHashCode()
{
return 31 * base.GetHashCode() + (m_query ?? (object)m_filter).GetHashCode();
}
}
}