529 lines
21 KiB
C#
529 lines
21 KiB
C#
/*
|
|
* 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 System;
|
|
|
|
using NUnit.Framework;
|
|
|
|
using WhitespaceAnalyzer = Lucene.Net.Analysis.WhitespaceAnalyzer;
|
|
using Document = Lucene.Net.Documents.Document;
|
|
using IndexReader = Lucene.Net.Index.IndexReader;
|
|
using IndexWriter = Lucene.Net.Index.IndexWriter;
|
|
using MultiReader = Lucene.Net.Index.MultiReader;
|
|
using MaxFieldLength = Lucene.Net.Index.IndexWriter.MaxFieldLength;
|
|
using RAMDirectory = Lucene.Net.Store.RAMDirectory;
|
|
using ReaderUtil = Lucene.Net.Util.ReaderUtil;
|
|
|
|
namespace Lucene.Net.Search
|
|
{
|
|
public class QueryUtils
|
|
{
|
|
[Serializable]
|
|
private class AnonymousClassQuery:Query
|
|
{
|
|
public override System.String ToString(System.String field)
|
|
{
|
|
return "My Whacky Query";
|
|
}
|
|
}
|
|
|
|
private class AnonymousClassCollector:Collector
|
|
{
|
|
public AnonymousClassCollector(int[] order, int[] opidx, int skip_op, IndexReader[] lastReader, float maxDiff, Query q, IndexSearcher s, int[] lastDoc)
|
|
{
|
|
InitBlock(order, opidx, skip_op, lastReader, maxDiff, q, s, lastDoc);
|
|
}
|
|
private void InitBlock(int[] order, int[] opidx, int skip_op, IndexReader[] lastReader, float maxDiff, Query q, IndexSearcher s, int[] lastDoc)
|
|
{
|
|
this.order = order;
|
|
this.opidx = opidx;
|
|
this.lastDoc = lastDoc;
|
|
this.skip_op = skip_op;
|
|
this.scorer = scorer;
|
|
this.lastReader = lastReader;
|
|
this.maxDiff = maxDiff;
|
|
this.q = q;
|
|
this.s = s;
|
|
}
|
|
|
|
private Scorer sc;
|
|
private IndexReader reader;
|
|
private Scorer scorer;
|
|
private int[] order;
|
|
private int[] lastDoc;
|
|
private int[] opidx;
|
|
private int skip_op;
|
|
private float maxDiff;
|
|
private Lucene.Net.Search.Query q;
|
|
private Lucene.Net.Search.IndexSearcher s;
|
|
private IndexReader[] lastReader;
|
|
|
|
public override void SetScorer(Scorer scorer)
|
|
{
|
|
this.sc = scorer;
|
|
}
|
|
|
|
public override void Collect(int doc)
|
|
{
|
|
float score = sc.Score();
|
|
lastDoc[0] = doc;
|
|
try
|
|
{
|
|
if (scorer == null)
|
|
{
|
|
Weight w = q.Weight(s);
|
|
scorer = w.Scorer(reader, true, false);
|
|
}
|
|
int op = order[(opidx[0]++) % order.Length];
|
|
// System.out.println(op==skip_op ?
|
|
// "skip("+(sdoc[0]+1)+")":"next()");
|
|
bool more = op == skip_op
|
|
? scorer.Advance(scorer.DocID() + 1) != DocIdSetIterator.NO_MORE_DOCS
|
|
: scorer.NextDoc() != DocIdSetIterator.NO_MORE_DOCS;
|
|
int scorerDoc = scorer.DocID();
|
|
float scorerScore = scorer.Score();
|
|
float scorerScore2 = scorer.Score();
|
|
float scoreDiff = System.Math.Abs(score - scorerScore);
|
|
float scorerDiff = System.Math.Abs(scorerScore2 - scorerScore);
|
|
if (!more || doc != scorerDoc || scoreDiff > maxDiff || scorerDiff > maxDiff)
|
|
{
|
|
System.Text.StringBuilder sbord = new System.Text.StringBuilder();
|
|
for (int i = 0; i < order.Length; i++)
|
|
sbord.Append(order[i] == skip_op?" skip()":" next()");
|
|
throw new System.SystemException("ERROR matching docs:" + "\n\t" + (doc != scorerDoc ? "--> " : "") + "scorerDoc=" +
|
|
scorerDoc + "\n\t" + (!more ? "--> " : "") + "tscorer.more=" + more + "\n\t" +
|
|
(scoreDiff > maxDiff ? "--> " : "") + "scorerScore=" + scorerScore +
|
|
" scoreDiff=" + scoreDiff + " maxDiff=" + maxDiff + "\n\t" +
|
|
(scorerDiff > maxDiff ? "--> " : "") + "scorerScore2=" + scorerScore2 +
|
|
" scorerDiff=" + scorerDiff + "\n\thitCollector.doc=" + doc + " score=" +
|
|
score + "\n\t Scorer=" + scorer + "\n\t Query=" + q + " " +
|
|
q.GetType().FullName + "\n\t Searcher=" + s + "\n\t Order=" + sbord +
|
|
"\n\t Op=" + (op == skip_op ? " skip()" : " next()"));
|
|
}
|
|
}
|
|
catch (System.IO.IOException e)
|
|
{
|
|
throw new System.SystemException("", e);
|
|
}
|
|
}
|
|
|
|
public override void SetNextReader(IndexReader reader, int docBase)
|
|
{
|
|
// confirm that skipping beyond the last doc, on the
|
|
// previous reader, hits NO_MORE_DOCS
|
|
if (lastReader[0] != null) {
|
|
IndexReader previousReader = lastReader[0];
|
|
Weight w = q.Weight(new IndexSearcher(previousReader));
|
|
Scorer scorer = w.Scorer(previousReader, true, false);
|
|
if (scorer != null) {
|
|
bool more = scorer.Advance(lastDoc[0] + 1) != DocIdSetIterator.NO_MORE_DOCS;
|
|
Assert.IsFalse(more, "query's last doc was "+ lastDoc[0] +" but skipTo("+(lastDoc[0]+1)+") got to "+scorer.DocID());
|
|
}
|
|
}
|
|
this.reader = reader;
|
|
this.scorer = null;
|
|
lastDoc[0] = -1;
|
|
}
|
|
|
|
public override bool AcceptsDocsOutOfOrder
|
|
{
|
|
get { return true; }
|
|
}
|
|
}
|
|
private class AnonymousClassCollector1:Collector
|
|
{
|
|
public AnonymousClassCollector1(int[] lastDoc, Lucene.Net.Search.Query q, Lucene.Net.Search.IndexSearcher s, float maxDiff, IndexReader[] lastReader)
|
|
{
|
|
InitBlock(lastDoc, q, s, maxDiff, lastReader);
|
|
}
|
|
private void InitBlock(int[] lastDoc, Lucene.Net.Search.Query q, Lucene.Net.Search.IndexSearcher s, float maxDiff, IndexReader[] lastReader)
|
|
{
|
|
this.lastDoc = lastDoc;
|
|
this.q = q;
|
|
this.s = s;
|
|
this.maxDiff = maxDiff;
|
|
this.lastReader = lastReader;
|
|
}
|
|
private int[] lastDoc;
|
|
private Lucene.Net.Search.Query q;
|
|
private Lucene.Net.Search.IndexSearcher s;
|
|
private float maxDiff;
|
|
private Scorer scorer;
|
|
private IndexReader reader;
|
|
private IndexReader[] lastReader;
|
|
|
|
public override void SetScorer(Scorer scorer)
|
|
{
|
|
this.scorer = scorer;
|
|
}
|
|
public override void Collect(int doc)
|
|
{
|
|
//System.out.println("doc="+doc);
|
|
float score = this.scorer.Score();
|
|
try
|
|
{
|
|
|
|
for (int i = lastDoc[0] + 1; i <= doc; i++)
|
|
{
|
|
Weight w = q.Weight(s);
|
|
Scorer scorer = w.Scorer(reader, true, false);
|
|
Assert.IsTrue(scorer.Advance(i) != DocIdSetIterator.NO_MORE_DOCS, "query collected " + doc + " but skipTo(" + i + ") says no more docs!");
|
|
Assert.AreEqual(doc, scorer.DocID(), "query collected " + doc + " but skipTo(" + i + ") got to " + scorer.DocID());
|
|
float skipToScore = scorer.Score();
|
|
Assert.AreEqual(skipToScore, scorer.Score(), maxDiff, "unstable skipTo(" + i + ") score!");
|
|
Assert.AreEqual(score, skipToScore, maxDiff, "query assigned doc " + doc + " a score of <" + score + "> but skipTo(" + i + ") has <" + skipToScore + ">!");
|
|
}
|
|
lastDoc[0] = doc;
|
|
}
|
|
catch (System.IO.IOException e)
|
|
{
|
|
throw new System.SystemException("", e);
|
|
}
|
|
}
|
|
public override void SetNextReader(IndexReader reader, int docBase)
|
|
{
|
|
// confirm that skipping beyond the last doc, on the
|
|
// previous reader, hits NO_MORE_DOCS
|
|
if (lastReader[0] != null)
|
|
{
|
|
IndexReader previousReader = lastReader[0];
|
|
Weight w = q.Weight(new IndexSearcher(previousReader));
|
|
Scorer scorer = w.Scorer(previousReader, true, false);
|
|
if (scorer != null)
|
|
{
|
|
bool more = scorer.Advance(lastDoc[0] + 1) != DocIdSetIterator.NO_MORE_DOCS;
|
|
Assert.IsFalse(more, "query's last doc was " + lastDoc[0] + " but skipTo(" + (lastDoc[0] + 1) + ") got to " + scorer.DocID());
|
|
}
|
|
}
|
|
|
|
this.reader = lastReader[0] = reader;
|
|
lastDoc[0] = -1;
|
|
}
|
|
|
|
public override bool AcceptsDocsOutOfOrder
|
|
{
|
|
get { return false; }
|
|
}
|
|
}
|
|
|
|
/// <summary>Check the types of things query objects should be able to do. </summary>
|
|
public static void Check(Query q)
|
|
{
|
|
CheckHashEquals(q);
|
|
}
|
|
|
|
/// <summary>check very basic hashCode and equals </summary>
|
|
public static void CheckHashEquals(Query q)
|
|
{
|
|
Query q2 = (Query) q.Clone();
|
|
CheckEqual(q, q2);
|
|
|
|
Query q3 = (Query) q.Clone();
|
|
q3.Boost = 7.21792348f;
|
|
CheckUnequal(q, q3);
|
|
|
|
// test that a class check is done so that no exception is thrown
|
|
// in the implementation of equals()
|
|
Query whacky = new AnonymousClassQuery();
|
|
whacky.Boost = q.Boost;
|
|
CheckUnequal(q, whacky);
|
|
}
|
|
|
|
public static void CheckEqual(Query q1, Query q2)
|
|
{
|
|
Assert.AreEqual(q1, q2);
|
|
Assert.AreEqual(q1.GetHashCode(), q2.GetHashCode());
|
|
}
|
|
|
|
public static void CheckUnequal(Query q1, Query q2)
|
|
{
|
|
Assert.IsTrue(!q1.Equals(q2));
|
|
Assert.IsTrue(!q2.Equals(q1));
|
|
|
|
// possible this test can fail on a hash collision... if that
|
|
// happens, please change test to use a different example.
|
|
Assert.IsTrue(q1.GetHashCode() != q2.GetHashCode());
|
|
}
|
|
|
|
/// <summary>deep check that explanations of a query 'score' correctly </summary>
|
|
public static void CheckExplanations(Query q, Searcher s)
|
|
{
|
|
CheckHits.CheckExplanations(q, null, s, true);
|
|
}
|
|
|
|
/// <summary> Various query sanity checks on a searcher, some checks are only done for
|
|
/// instanceof IndexSearcher.
|
|
///
|
|
/// </summary>
|
|
/// <seealso cref="Check(Query)">
|
|
/// </seealso>
|
|
/// <seealso cref="checkFirstSkipTo">
|
|
/// </seealso>
|
|
/// <seealso cref="checkSkipTo">
|
|
/// </seealso>
|
|
/// <seealso cref="checkExplanations">
|
|
/// </seealso>
|
|
/// <seealso cref="checkSerialization">
|
|
/// </seealso>
|
|
/// <seealso cref="checkEqual">
|
|
/// </seealso>
|
|
public static void Check(Query q1, Searcher s)
|
|
{
|
|
Check(q1, s, true);
|
|
}
|
|
private static void Check(Query q1, Searcher s, bool wrap)
|
|
{
|
|
try
|
|
{
|
|
Check(q1);
|
|
if (s != null)
|
|
{
|
|
if (s is IndexSearcher)
|
|
{
|
|
IndexSearcher is_Renamed = (IndexSearcher) s;
|
|
CheckFirstSkipTo(q1, is_Renamed);
|
|
CheckSkipTo(q1, is_Renamed);
|
|
if (wrap)
|
|
{
|
|
Check(q1, WrapUnderlyingReader(is_Renamed, - 1), false);
|
|
Check(q1, WrapUnderlyingReader(is_Renamed, 0), false);
|
|
Check(q1, WrapUnderlyingReader(is_Renamed, + 1), false);
|
|
}
|
|
}
|
|
if (wrap)
|
|
{
|
|
Check(q1, WrapSearcher(s, - 1), false);
|
|
Check(q1, WrapSearcher(s, 0), false);
|
|
Check(q1, WrapSearcher(s, + 1), false);
|
|
}
|
|
CheckExplanations(q1, s);
|
|
CheckSerialization(q1, s);
|
|
|
|
Query q2 = (Query) q1.Clone();
|
|
CheckEqual(s.Rewrite(q1), s.Rewrite(q2));
|
|
}
|
|
}
|
|
catch (System.IO.IOException e)
|
|
{
|
|
throw new System.SystemException("", e);
|
|
}
|
|
}
|
|
|
|
/// <summary> Given an IndexSearcher, returns a new IndexSearcher whose IndexReader
|
|
/// is a MultiReader containing the Reader of the original IndexSearcher,
|
|
/// as well as several "empty" IndexReaders -- some of which will have
|
|
/// deleted documents in them. This new IndexSearcher should
|
|
/// behave exactly the same as the original IndexSearcher.
|
|
/// </summary>
|
|
/// <param name="s">the searcher to wrap
|
|
/// </param>
|
|
/// <param name="edge">if negative, s will be the first sub; if 0, s will be in the middle, if positive s will be the last sub
|
|
/// </param>
|
|
public static IndexSearcher WrapUnderlyingReader(IndexSearcher s, int edge)
|
|
{
|
|
|
|
IndexReader r = s.IndexReader;
|
|
|
|
// we can't put deleted docs before the nested reader, because
|
|
// it will throw off the docIds
|
|
IndexReader[] readers = new IndexReader[]
|
|
{
|
|
edge < 0 ? r : IndexReader.Open(MakeEmptyIndex(0), true),
|
|
IndexReader.Open(MakeEmptyIndex(0), true),
|
|
new MultiReader(new IndexReader[]
|
|
{
|
|
IndexReader.Open(MakeEmptyIndex(edge < 0 ? 4 : 0), true),
|
|
IndexReader.Open(MakeEmptyIndex(0), true),
|
|
0 == edge ? r : IndexReader.Open(MakeEmptyIndex(0), true)
|
|
}),
|
|
IndexReader.Open(MakeEmptyIndex(0 < edge ? 0 : 7), true),
|
|
IndexReader.Open(MakeEmptyIndex(0), true),
|
|
new MultiReader(new IndexReader[]
|
|
{
|
|
IndexReader.Open(MakeEmptyIndex(0 < edge ? 0 : 5), true),
|
|
IndexReader.Open(MakeEmptyIndex(0), true),
|
|
0 < edge ? r : IndexReader.Open(MakeEmptyIndex(0), true)
|
|
})
|
|
};
|
|
IndexSearcher out_Renamed = new IndexSearcher(new MultiReader(readers));
|
|
out_Renamed.Similarity = s.Similarity;
|
|
return out_Renamed;
|
|
}
|
|
/// <summary> Given a Searcher, returns a new MultiSearcher wrapping the
|
|
/// the original Searcher,
|
|
/// as well as several "empty" IndexSearchers -- some of which will have
|
|
/// deleted documents in them. This new MultiSearcher
|
|
/// should behave exactly the same as the original Searcher.
|
|
/// </summary>
|
|
/// <param name="s">the Searcher to wrap
|
|
/// </param>
|
|
/// <param name="edge">if negative, s will be the first sub; if 0, s will be in hte middle, if positive s will be the last sub
|
|
/// </param>
|
|
public static MultiSearcher WrapSearcher(Searcher s, int edge)
|
|
{
|
|
|
|
// we can't put deleted docs before the nested reader, because
|
|
// it will through off the docIds
|
|
Searcher[] searchers = new Searcher[]
|
|
{
|
|
edge < 0 ? s : new IndexSearcher(MakeEmptyIndex(0), true),
|
|
new MultiSearcher(new Searcher[]
|
|
{
|
|
new IndexSearcher(MakeEmptyIndex(edge < 0 ? 65 : 0), true),
|
|
new IndexSearcher(MakeEmptyIndex(0), true),
|
|
0 == edge ? s : new IndexSearcher(MakeEmptyIndex(0), true)
|
|
}),
|
|
new IndexSearcher(MakeEmptyIndex(0 < edge ? 0 : 3), true),
|
|
new IndexSearcher(MakeEmptyIndex(0), true),
|
|
new MultiSearcher(new Searcher[]
|
|
{
|
|
new IndexSearcher(MakeEmptyIndex(0 < edge ? 0 : 5), true),
|
|
new IndexSearcher(MakeEmptyIndex(0), true),
|
|
0 < edge ? s : new IndexSearcher(MakeEmptyIndex(0), true)
|
|
})
|
|
};
|
|
MultiSearcher out_Renamed = new MultiSearcher(searchers);
|
|
out_Renamed.Similarity = s.Similarity;
|
|
return out_Renamed;
|
|
}
|
|
|
|
private static RAMDirectory MakeEmptyIndex(int numDeletedDocs)
|
|
{
|
|
RAMDirectory d = new RAMDirectory();
|
|
IndexWriter w = new IndexWriter(d, new WhitespaceAnalyzer(), true, MaxFieldLength.LIMITED);
|
|
for (int i = 0; i < numDeletedDocs; i++)
|
|
{
|
|
w.AddDocument(new Document());
|
|
}
|
|
w.Commit();
|
|
w.DeleteDocuments(new MatchAllDocsQuery());
|
|
w.Commit();
|
|
|
|
if (0 < numDeletedDocs)
|
|
Assert.IsTrue(w.HasDeletions(), "writer has no deletions");
|
|
|
|
Assert.AreEqual(numDeletedDocs, w.MaxDoc(), "writer is missing some deleted docs");
|
|
Assert.AreEqual(0, w.NumDocs(), "writer has non-deleted docs");
|
|
w.Close();
|
|
IndexReader r = IndexReader.Open(d, true);
|
|
Assert.AreEqual(numDeletedDocs, r.NumDeletedDocs, "reader has wrong number of deleted docs");
|
|
r.Close();
|
|
return d;
|
|
}
|
|
|
|
|
|
/// <summary>check that the query weight is serializable. </summary>
|
|
/// <throws> IOException if serialization check fail. </throws>
|
|
private static void CheckSerialization(Query q, Searcher s)
|
|
{
|
|
Weight w = q.Weight(s);
|
|
try
|
|
{
|
|
System.IO.MemoryStream bos = new System.IO.MemoryStream();
|
|
System.IO.BinaryWriter oos = new System.IO.BinaryWriter(bos);
|
|
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
|
|
formatter.Serialize(oos.BaseStream, w);
|
|
oos.Close();
|
|
System.IO.BinaryReader ois = new System.IO.BinaryReader(new System.IO.MemoryStream(bos.ToArray()));
|
|
formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
|
|
formatter.Deserialize(ois.BaseStream);
|
|
ois.Close();
|
|
|
|
//skip equals() test for now - most weights don't override equals() and we won't add this just for the tests.
|
|
//TestCase.Assert.AreEqual(w2,w,"writeObject(w) != w. ("+w+")");
|
|
}
|
|
catch (System.Exception e)
|
|
{
|
|
System.IO.IOException e2 = new System.IO.IOException("Serialization failed for " + w, e);
|
|
throw e2;
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>alternate scorer skipTo(),skipTo(),next(),next(),skipTo(),skipTo(), etc
|
|
/// and ensure a hitcollector receives same docs and scores
|
|
/// </summary>
|
|
public static void CheckSkipTo(Query q, IndexSearcher s)
|
|
{
|
|
//System.out.println("Checking "+q);
|
|
|
|
if (q.Weight(s).GetScoresDocsOutOfOrder())
|
|
return ; // in this case order of skipTo() might differ from that of next().
|
|
|
|
int skip_op = 0;
|
|
int next_op = 1;
|
|
int[][] orders = new int[][]{new int[]{next_op}, new int[]{skip_op}, new int[]{skip_op, next_op}, new int[]{next_op, skip_op}, new int[]{skip_op, skip_op, next_op, next_op}, new int[]{next_op, next_op, skip_op, skip_op}, new int[]{skip_op, skip_op, skip_op, next_op, next_op}};
|
|
for (int k = 0; k < orders.Length; k++)
|
|
{
|
|
|
|
int[] order = orders[k];
|
|
// System.out.print("Order:");for (int i = 0; i < order.length; i++)
|
|
// System.out.print(order[i]==skip_op ? " skip()":" next()");
|
|
// System.out.println();
|
|
int[] opidx = new int[]{0};
|
|
int[] lastDoc = new[] {-1};
|
|
|
|
// FUTURE: ensure scorer.doc()==-1
|
|
|
|
float maxDiff = 1e-5f;
|
|
IndexReader[] lastReader = new IndexReader[] {null};
|
|
|
|
s.Search(q, new AnonymousClassCollector(order, opidx, skip_op, lastReader, maxDiff, q, s, lastDoc));
|
|
|
|
if (lastReader[0] != null)
|
|
{
|
|
// Confirm that skipping beyond the last doc, on the
|
|
// previous reader, hits NO_MORE_DOCS
|
|
IndexReader previousReader = lastReader[0];
|
|
Weight w = q.Weight(new IndexSearcher(previousReader));
|
|
Scorer scorer = w.Scorer(previousReader, true, false);
|
|
if (scorer != null)
|
|
{
|
|
bool more = scorer.Advance(lastDoc[0] + 1) != DocIdSetIterator.NO_MORE_DOCS;
|
|
Assert.IsFalse(more, "query's last doc was " + lastDoc[0] + " but skipTo(" + (lastDoc[0] + 1) + ") got to " + scorer.DocID());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// check that first skip on just created scorers always goes to the right doc
|
|
private static void CheckFirstSkipTo(Query q, IndexSearcher s)
|
|
{
|
|
//System.out.println("checkFirstSkipTo: "+q);
|
|
float maxDiff = 1e-4f; //{{Lucene.Net-2.9.1}}Intentional diversion from Java Lucene
|
|
int[] lastDoc = new int[]{- 1};
|
|
IndexReader[] lastReader = {null};
|
|
|
|
s.Search(q, new AnonymousClassCollector1(lastDoc, q, s, maxDiff, lastReader));
|
|
|
|
if(lastReader[0] != null)
|
|
{
|
|
// confirm that skipping beyond the last doc, on the
|
|
// previous reader, hits NO_MORE_DOCS
|
|
IndexReader previousReader = lastReader[0];
|
|
Weight w = q.Weight(new IndexSearcher(previousReader));
|
|
Scorer scorer = w.Scorer(previousReader, true, false);
|
|
|
|
if (scorer != null)
|
|
{
|
|
bool more = scorer.Advance(lastDoc[0] + 1) != DocIdSetIterator.NO_MORE_DOCS;
|
|
Assert.IsFalse(more, "query's last doc was " + lastDoc[0] + " but skipTo(" + (lastDoc[0] + 1) + ") got to " + scorer.DocID());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |