blob: ba671e7a27e140d8be2145fdc5bee14895cf08b1 [file] [log] [blame]
/*
* 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());
}
}
}
}
}