| // Lucene version compatibility level 4.8.1 |
| using Lucene.Net.Index; |
| using Lucene.Net.Queries.Function.DocValues; |
| using Lucene.Net.Search; |
| using Lucene.Net.Util; |
| using Lucene.Net.Util.Mutable; |
| using System; |
| using System.Collections; |
| using System.IO; |
| |
| namespace Lucene.Net.Queries.Function.ValueSources |
| { |
| /* |
| * 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. |
| */ |
| |
| /// <summary> |
| /// <see cref="QueryValueSource"/> returns the relevance score of the query |
| /// </summary> |
| public class QueryValueSource : ValueSource |
| { |
| internal readonly Query q; |
| internal readonly float defVal; |
| |
| public QueryValueSource(Query q, float defVal) |
| { |
| this.q = q; |
| this.defVal = defVal; |
| } |
| |
| public virtual Query Query => q; |
| |
| public virtual float DefaultValue => defVal; |
| |
| |
| public override string GetDescription() |
| { |
| return "query(" + q + ",def=" + defVal + ")"; |
| } |
| |
| public override FunctionValues GetValues(IDictionary fcontext, AtomicReaderContext readerContext) |
| { |
| return new QueryDocValues(this, readerContext, fcontext); |
| } |
| |
| public override int GetHashCode() |
| { |
| return q.GetHashCode() * 29; |
| } |
| |
| public override bool Equals(object o) |
| { |
| if (!(o is QueryValueSource other)) |
| return false; |
| return this.q.Equals(other.q) && this.defVal == other.defVal; |
| } |
| |
| public override void CreateWeight(IDictionary context, IndexSearcher searcher) |
| { |
| Weight w = searcher.CreateNormalizedWeight(q); |
| context[this] = w; |
| } |
| } |
| |
| |
| internal class QueryDocValues : SingleDocValues |
| { |
| internal readonly AtomicReaderContext readerContext; |
| internal readonly IBits acceptDocs; |
| internal readonly Weight weight; |
| internal readonly float defVal; |
| internal readonly IDictionary fcontext; |
| internal readonly Query q; |
| |
| internal Scorer scorer; |
| internal int scorerDoc; // the document the scorer is on |
| internal bool noMatches = false; |
| |
| // the last document requested... start off with high value |
| // to trigger a scorer reset on first access. |
| internal int lastDocRequested = int.MaxValue; |
| |
| public QueryDocValues(QueryValueSource vs, AtomicReaderContext readerContext, IDictionary fcontext) |
| : base(vs) |
| { |
| |
| this.readerContext = readerContext; |
| this.acceptDocs = readerContext.AtomicReader.LiveDocs; |
| this.defVal = vs.defVal; |
| this.q = vs.q; |
| this.fcontext = fcontext; |
| |
| Weight w = fcontext == null ? null : (Weight)fcontext[vs]; |
| if (w == null) |
| { |
| IndexSearcher weightSearcher; |
| if (fcontext == null) |
| { |
| weightSearcher = new IndexSearcher(ReaderUtil.GetTopLevelContext(readerContext)); |
| } |
| else |
| { |
| weightSearcher = (IndexSearcher)fcontext["searcher"]; |
| if (weightSearcher == null) |
| { |
| weightSearcher = new IndexSearcher(ReaderUtil.GetTopLevelContext(readerContext)); |
| } |
| } |
| vs.CreateWeight(fcontext, weightSearcher); |
| w = (Weight)fcontext[vs]; |
| } |
| weight = w; |
| } |
| |
| /// <summary> |
| /// NOTE: This was floatVal() in Lucene |
| /// </summary> |
| public override float SingleVal(int doc) |
| { |
| try |
| { |
| if (doc < lastDocRequested) |
| { |
| if (noMatches) |
| { |
| return defVal; |
| } |
| scorer = weight.GetScorer(readerContext, acceptDocs); |
| if (scorer == null) |
| { |
| noMatches = true; |
| return defVal; |
| } |
| scorerDoc = -1; |
| } |
| lastDocRequested = doc; |
| |
| if (scorerDoc < doc) |
| { |
| scorerDoc = scorer.Advance(doc); |
| } |
| |
| if (scorerDoc > doc) |
| { |
| // query doesn't match this document... either because we hit the |
| // end, or because the next doc is after this doc. |
| return defVal; |
| } |
| |
| // a match! |
| return scorer.GetScore(); |
| } |
| catch (IOException e) |
| { |
| throw new Exception("caught exception in QueryDocVals(" + q + ") doc=" + doc, e); |
| } |
| } |
| |
| public override bool Exists(int doc) |
| { |
| try |
| { |
| if (doc < lastDocRequested) |
| { |
| if (noMatches) |
| { |
| return false; |
| } |
| scorer = weight.GetScorer(readerContext, acceptDocs); |
| scorerDoc = -1; |
| if (scorer == null) |
| { |
| noMatches = true; |
| return false; |
| } |
| } |
| lastDocRequested = doc; |
| |
| if (scorerDoc < doc) |
| { |
| scorerDoc = scorer.Advance(doc); |
| } |
| |
| if (scorerDoc > doc) |
| { |
| // query doesn't match this document... either because we hit the |
| // end, or because the next doc is after this doc. |
| return false; |
| } |
| |
| // a match! |
| return true; |
| } |
| catch (IOException e) |
| { |
| throw new Exception("caught exception in QueryDocVals(" + q + ") doc=" + doc, e); |
| } |
| } |
| |
| public override object ObjectVal(int doc) |
| { |
| try |
| { |
| return Exists(doc) ? scorer.GetScore() : (float?)null; |
| } |
| catch (IOException e) |
| { |
| throw new Exception("caught exception in QueryDocVals(" + q + ") doc=" + doc, e); |
| } |
| } |
| |
| public override ValueFiller GetValueFiller() |
| { |
| // |
| // TODO: if we want to support more than one value-filler or a value-filler in conjunction with |
| // the FunctionValues, then members like "scorer" should be per ValueFiller instance. |
| // Or we can say that the user should just instantiate multiple FunctionValues. |
| // |
| return new ValueFiller.AnonymousValueFiller<MutableValueSingle>(new MutableValueSingle(), fillValue: (doc, mutableValue) => |
| { |
| try |
| { |
| if (noMatches) |
| { |
| mutableValue.Value = defVal; |
| mutableValue.Exists = false; |
| return; |
| } |
| scorer = weight.GetScorer(readerContext, acceptDocs); |
| scorerDoc = -1; |
| if (scorer == null) |
| { |
| noMatches = true; |
| mutableValue.Value = defVal; |
| mutableValue.Exists = false; |
| return; |
| } |
| lastDocRequested = doc; |
| |
| if (scorerDoc < doc) |
| { |
| scorerDoc = scorer.Advance(doc); |
| } |
| |
| if (scorerDoc > doc) |
| { |
| // query doesn't match this document... either because we hit the |
| // end, or because the next doc is after this doc. |
| mutableValue.Value = defVal; |
| mutableValue.Exists = false; |
| return; |
| } |
| |
| // a match! |
| mutableValue.Value = scorer.GetScore(); |
| mutableValue.Exists = true; |
| } |
| catch (IOException e) |
| { |
| throw new Exception("caught exception in QueryDocVals(" + q + ") doc=" + doc, e); |
| } |
| }); |
| } |
| |
| public override string ToString(int doc) |
| { |
| return "query(" + q + ",def=" + defVal + ")=" + SingleVal(doc); |
| } |
| } |
| } |