/* * 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; } } } /// Check the types of things query objects should be able to do. public static void Check(Query q) { CheckHashEquals(q); } /// check very basic hashCode and equals 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()); } /// deep check that explanations of a query 'score' correctly public static void CheckExplanations(Query q, Searcher s) { CheckHits.CheckExplanations(q, null, s, true); } /// Various query sanity checks on a searcher, some checks are only done for /// instanceof IndexSearcher. /// /// /// /// /// /// /// /// /// /// /// /// /// /// 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); } } /// 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. /// /// the searcher to wrap /// /// if negative, s will be the first sub; if 0, s will be in the middle, if positive s will be the last sub /// 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; } /// 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. /// /// the Searcher to wrap /// /// if negative, s will be the first sub; if 0, s will be in hte middle, if positive s will be the last sub /// 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; } /// check that the query weight is serializable. /// IOException if serialization check fail. 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; } } /// alternate scorer skipTo(),skipTo(),next(),next(),skipTo(),skipTo(), etc /// and ensure a hitcollector receives same docs and scores /// 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()); } } } } }