http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestCustomSearcherSort.cs ---------------------------------------------------------------------- diff --git a/src/Lucene.Net.Tests/Search/TestCustomSearcherSort.cs b/src/Lucene.Net.Tests/Search/TestCustomSearcherSort.cs new file mode 100644 index 0000000..0511e75 --- /dev/null +++ b/src/Lucene.Net.Tests/Search/TestCustomSearcherSort.cs @@ -0,0 +1,262 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using Lucene.Net.Documents; + +namespace Lucene.Net.Search +{ + using NUnit.Framework; + + /// <summary> + /// Copyright 2005 The Apache Software Foundation + /// + /// Licensed 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> + + using DateTools = DateTools; + using Directory = Lucene.Net.Store.Directory; + using Document = Documents.Document; + using Field = Field; + using IndexReader = Lucene.Net.Index.IndexReader; + using LuceneTestCase = Lucene.Net.Util.LuceneTestCase; + using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter; + using Term = Lucene.Net.Index.Term; + + /// <summary> + /// Unit test for sorting code. </summary> + [TestFixture] + public class TestCustomSearcherSort : LuceneTestCase + { + private Directory Index = null; + private IndexReader Reader; + private Query Query = null; + + // reduced from 20000 to 2000 to speed up test... + private int INDEX_SIZE; + + /// <summary> + /// Create index and query for test cases. + /// </summary> + [SetUp] + public override void SetUp() + { + base.SetUp(); + INDEX_SIZE = AtLeast(2000); + Index = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), Index, Similarity, TimeZone); + RandomGen random = new RandomGen(this, Random()); + for (int i = 0; i < INDEX_SIZE; ++i) // don't decrease; if to low the + { + // problem doesn't show up + Document doc = new Document(); + if ((i % 5) != 0) // some documents must not have an entry in the first + { + // sort field + doc.Add(NewStringField("publicationDate_", random.LuceneDate, Field.Store.YES)); + } + if ((i % 7) == 0) // some documents to match the query (see below) + { + doc.Add(NewTextField("content", "test", Field.Store.YES)); + } + // every document has a defined 'mandant' field + doc.Add(NewStringField("mandant", Convert.ToString(i % 3), Field.Store.YES)); + writer.AddDocument(doc); + } + Reader = writer.Reader; + writer.Dispose(); + Query = new TermQuery(new Term("content", "test")); + } + + [TearDown] + public override void TearDown() + { + Reader.Dispose(); + Index.Dispose(); + base.TearDown(); + } + + /// <summary> + /// Run the test using two CustomSearcher instances. + /// </summary> + [Test] + public virtual void TestFieldSortCustomSearcher() + { + // log("Run testFieldSortCustomSearcher"); + // define the sort criteria + Sort custSort = new Sort(new SortField("publicationDate_", SortFieldType.STRING), SortField.FIELD_SCORE); + IndexSearcher searcher = new CustomSearcher(this, Reader, 2); + // search and check hits + MatchHits(searcher, custSort); + } + + /// <summary> + /// Run the test using one CustomSearcher wrapped by a MultiSearcher. + /// </summary> + [Test] + public virtual void TestFieldSortSingleSearcher() + { + // log("Run testFieldSortSingleSearcher"); + // define the sort criteria + Sort custSort = new Sort(new SortField("publicationDate_", SortFieldType.STRING), SortField.FIELD_SCORE); + IndexSearcher searcher = new CustomSearcher(this, Reader, 2); + // search and check hits + MatchHits(searcher, custSort); + } + + // make sure the documents returned by the search match the expected list + private void MatchHits(IndexSearcher searcher, Sort sort) + { + // make a query without sorting first + ScoreDoc[] hitsByRank = searcher.Search(Query, null, int.MaxValue).ScoreDocs; + CheckHits(hitsByRank, "Sort by rank: "); // check for duplicates + IDictionary<int?, int?> resultMap = new SortedDictionary<int?, int?>(); + // store hits in TreeMap - TreeMap does not allow duplicates; existing + // entries are silently overwritten + for (int hitid = 0; hitid < hitsByRank.Length; ++hitid) + { + resultMap[Convert.ToInt32(hitsByRank[hitid].Doc)] = Convert.ToInt32(hitid); // Value: Hits-Objekt Index - Key: Lucene + // Document ID + } + + // now make a query using the sort criteria + ScoreDoc[] resultSort = searcher.Search(Query, null, int.MaxValue, sort).ScoreDocs; + CheckHits(resultSort, "Sort by custom criteria: "); // check for duplicates + + // besides the sorting both sets of hits must be identical + for (int hitid = 0; hitid < resultSort.Length; ++hitid) + { + int? idHitDate = Convert.ToInt32(resultSort[hitid].Doc); // document ID + // from sorted + // search + if (!resultMap.ContainsKey(idHitDate)) + { + Log("ID " + idHitDate + " not found. Possibliy a duplicate."); + } + Assert.IsTrue(resultMap.ContainsKey(idHitDate)); // same ID must be in the + // Map from the rank-sorted + // search + // every hit must appear once in both result sets --> remove it from the + // Map. + // At the end the Map must be empty! + resultMap.Remove(idHitDate); + } + if (resultMap.Count == 0) + { + // log("All hits matched"); + } + else + { + Log("Couldn't match " + resultMap.Count + " hits."); + } + Assert.AreEqual(resultMap.Count, 0); + } + + /// <summary> + /// Check the hits for duplicates. + /// </summary> + private void CheckHits(ScoreDoc[] hits, string prefix) + { + if (hits != null) + { + IDictionary<int?, int?> idMap = new SortedDictionary<int?, int?>(); + for (int docnum = 0; docnum < hits.Length; ++docnum) + { + int? luceneId = null; + + luceneId = Convert.ToInt32(hits[docnum].Doc); + if (idMap.ContainsKey(luceneId)) + { + StringBuilder message = new StringBuilder(prefix); + message.Append("Duplicate key for hit index = "); + message.Append(docnum); + message.Append(", previous index = "); + message.Append((idMap[luceneId]).ToString()); + message.Append(", Lucene ID = "); + message.Append(luceneId); + Log(message.ToString()); + } + else + { + idMap[luceneId] = Convert.ToInt32(docnum); + } + } + } + } + + // Simply write to console - choosen to be independant of log4j etc + private void Log(string message) + { + if (VERBOSE) + { + Console.WriteLine(message); + } + } + + public class CustomSearcher : IndexSearcher + { + private readonly TestCustomSearcherSort OuterInstance; + + internal int Switcher; + + public CustomSearcher(TestCustomSearcherSort outerInstance, IndexReader r, int switcher) + : base(r) + { + this.OuterInstance = outerInstance; + this.Switcher = switcher; + } + + public override TopFieldDocs Search(Query query, Filter filter, int nDocs, Sort sort) + { + BooleanQuery bq = new BooleanQuery(); + bq.Add(query, Occur.MUST); + bq.Add(new TermQuery(new Term("mandant", Convert.ToString(Switcher))), Occur.MUST); + return base.Search(bq, filter, nDocs, sort); + } + + public override TopDocs Search(Query query, Filter filter, int nDocs) + { + BooleanQuery bq = new BooleanQuery(); + bq.Add(query, Occur.MUST); + bq.Add(new TermQuery(new Term("mandant", Convert.ToString(Switcher))), Occur.MUST); + return base.Search(bq, filter, nDocs); + } + } + + private class RandomGen + { + private readonly TestCustomSearcherSort OuterInstance; + + internal RandomGen(TestCustomSearcherSort outerInstance, Random random) + { + this.OuterInstance = outerInstance; + this.Random = random; + @base = new DateTime(1980, 1, 1); + } + + internal Random Random; + + // we use the default Locale/TZ since LuceneTestCase randomizes it + internal DateTime @base; + + // Just to generate some different Lucene Date strings + internal virtual string LuceneDate + { + get + { + return DateTools.TimeToString((DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) + Random.Next() - int.MinValue, DateTools.Resolution.DAY); + } + } + } + } +} \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestDateFilter.cs ---------------------------------------------------------------------- diff --git a/src/Lucene.Net.Tests/Search/TestDateFilter.cs b/src/Lucene.Net.Tests/Search/TestDateFilter.cs new file mode 100644 index 0000000..11e62e1 --- /dev/null +++ b/src/Lucene.Net.Tests/Search/TestDateFilter.cs @@ -0,0 +1,165 @@ +using System; +using Lucene.Net.Attributes; +using Lucene.Net.Documents; + +namespace Lucene.Net.Search +{ + using NUnit.Framework; + using DateTools = DateTools; + using Directory = Lucene.Net.Store.Directory; + using Document = Documents.Document; + + /* + * 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 Field = Field; + using IndexReader = Lucene.Net.Index.IndexReader; + using LuceneTestCase = Lucene.Net.Util.LuceneTestCase; + using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter; + using Term = Lucene.Net.Index.Term; + + /// <summary> + /// DateFilter JUnit tests. + /// + /// + /// </summary> + [TestFixture] + public class TestDateFilter : LuceneTestCase + { + /// + [OneTimeSetUp] + public virtual void TestBefore() + { + // create an index + Directory indexStore = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), indexStore, Similarity, TimeZone); + + long now = DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond; + + Document doc = new Document(); + // add time that is in the past + doc.Add(NewStringField("datefield", DateTools.TimeToString(now - 1000, DateTools.Resolution.MILLISECOND), Field.Store.YES)); + doc.Add(NewTextField("body", "Today is a very sunny day in New York City", Field.Store.YES)); + writer.AddDocument(doc); + + IndexReader reader = writer.Reader; + writer.Dispose(); + IndexSearcher searcher = NewSearcher(reader); + + // filter that should preserve matches + // DateFilter df1 = DateFilter.Before("datefield", now); + TermRangeFilter df1 = TermRangeFilter.NewStringRange("datefield", DateTools.TimeToString(now - 2000, DateTools.Resolution.MILLISECOND), DateTools.TimeToString(now, DateTools.Resolution.MILLISECOND), false, true); + // filter that should discard matches + // DateFilter df2 = DateFilter.Before("datefield", now - 999999); + TermRangeFilter df2 = TermRangeFilter.NewStringRange("datefield", DateTools.TimeToString(0, DateTools.Resolution.MILLISECOND), DateTools.TimeToString(now - 2000, DateTools.Resolution.MILLISECOND), true, false); + + // search something that doesn't exist with DateFilter + Query query1 = new TermQuery(new Term("body", "NoMatchForthis")); + + // search for something that does exists + Query query2 = new TermQuery(new Term("body", "sunny")); + + ScoreDoc[] result; + + // ensure that queries return expected results without DateFilter first + result = searcher.Search(query1, null, 1000).ScoreDocs; + Assert.AreEqual(0, result.Length); + + result = searcher.Search(query2, null, 1000).ScoreDocs; + Assert.AreEqual(1, result.Length); + + // run queries with DateFilter + result = searcher.Search(query1, df1, 1000).ScoreDocs; + Assert.AreEqual(0, result.Length); + + result = searcher.Search(query1, df2, 1000).ScoreDocs; + Assert.AreEqual(0, result.Length); + + result = searcher.Search(query2, df1, 1000).ScoreDocs; + Assert.AreEqual(1, result.Length); + + result = searcher.Search(query2, df2, 1000).ScoreDocs; + Assert.AreEqual(0, result.Length); + reader.Dispose(); + indexStore.Dispose(); + } + + [Test, LuceneNetSpecific] + public void Test() + { + // noop, required for the before and after tests to run + } + + /// + [OneTimeTearDown] + public virtual void TestAfter() + { + // create an index + Directory indexStore = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), indexStore, Similarity, TimeZone); + + long now = DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond; + + Document doc = new Document(); + // add time that is in the future + doc.Add(NewStringField("datefield", DateTools.TimeToString(now + 888888, DateTools.Resolution.MILLISECOND), Field.Store.YES)); + doc.Add(NewTextField("body", "Today is a very sunny day in New York City", Field.Store.YES)); + writer.AddDocument(doc); + + IndexReader reader = writer.Reader; + writer.Dispose(); + IndexSearcher searcher = NewSearcher(reader); + + // filter that should preserve matches + // DateFilter df1 = DateFilter.After("datefield", now); + TermRangeFilter df1 = TermRangeFilter.NewStringRange("datefield", DateTools.TimeToString(now, DateTools.Resolution.MILLISECOND), DateTools.TimeToString(now + 999999, DateTools.Resolution.MILLISECOND), true, false); + // filter that should discard matches + // DateFilter df2 = DateFilter.After("datefield", now + 999999); + TermRangeFilter df2 = TermRangeFilter.NewStringRange("datefield", DateTools.TimeToString(now + 999999, DateTools.Resolution.MILLISECOND), DateTools.TimeToString(now + 999999999, DateTools.Resolution.MILLISECOND), false, true); + + // search something that doesn't exist with DateFilter + Query query1 = new TermQuery(new Term("body", "NoMatchForthis")); + + // search for something that does exists + Query query2 = new TermQuery(new Term("body", "sunny")); + + ScoreDoc[] result; + + // ensure that queries return expected results without DateFilter first + result = searcher.Search(query1, null, 1000).ScoreDocs; + Assert.AreEqual(0, result.Length); + + result = searcher.Search(query2, null, 1000).ScoreDocs; + Assert.AreEqual(1, result.Length); + + // run queries with DateFilter + result = searcher.Search(query1, df1, 1000).ScoreDocs; + Assert.AreEqual(0, result.Length); + + result = searcher.Search(query1, df2, 1000).ScoreDocs; + Assert.AreEqual(0, result.Length); + + result = searcher.Search(query2, df1, 1000).ScoreDocs; + Assert.AreEqual(1, result.Length); + + result = searcher.Search(query2, df2, 1000).ScoreDocs; + Assert.AreEqual(0, result.Length); + reader.Dispose(); + indexStore.Dispose(); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestDateSort.cs ---------------------------------------------------------------------- diff --git a/src/Lucene.Net.Tests/Search/TestDateSort.cs b/src/Lucene.Net.Tests/Search/TestDateSort.cs new file mode 100644 index 0000000..3c45c9b --- /dev/null +++ b/src/Lucene.Net.Tests/Search/TestDateSort.cs @@ -0,0 +1,125 @@ +using Lucene.Net.Documents; + +namespace Lucene.Net.Search +{ + using Lucene.Net.Support; + using NUnit.Framework; + using DateTools = DateTools; + using Directory = Lucene.Net.Store.Directory; + using Document = Documents.Document; + using Field = Field; + using IndexReader = Lucene.Net.Index.IndexReader; + using LuceneTestCase = Lucene.Net.Util.LuceneTestCase; + using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter; + + /* + * 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 Term = Lucene.Net.Index.Term; + + /// <summary> + /// Test date sorting, i.e. auto-sorting of fields with type "long". + /// See http://issues.apache.org/jira/browse/LUCENE-1045 + /// </summary> + [TestFixture] + public class TestDateSort : LuceneTestCase + { + private const string TEXT_FIELD = "text"; + private const string DATE_TIME_FIELD = "dateTime"; + + private Directory Directory; + private IndexReader Reader; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + // Create an index writer. + Directory = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), Directory, Similarity, TimeZone); + + // oldest doc: + // Add the first document. text = "Document 1" dateTime = Oct 10 03:25:22 EDT 2007 + writer.AddDocument(CreateDocument("Document 1", 1192001122000L)); + // Add the second document. text = "Document 2" dateTime = Oct 10 03:25:26 EDT 2007 + writer.AddDocument(CreateDocument("Document 2", 1192001126000L)); + // Add the third document. text = "Document 3" dateTime = Oct 11 07:12:13 EDT 2007 + writer.AddDocument(CreateDocument("Document 3", 1192101133000L)); + // Add the fourth document. text = "Document 4" dateTime = Oct 11 08:02:09 EDT 2007 + writer.AddDocument(CreateDocument("Document 4", 1192104129000L)); + // latest doc: + // Add the fifth document. text = "Document 5" dateTime = Oct 12 13:25:43 EDT 2007 + writer.AddDocument(CreateDocument("Document 5", 1192209943000L)); + + Reader = writer.Reader; + writer.Dispose(); + } + + [TearDown] + public override void TearDown() + { + Reader.Dispose(); + Directory.Dispose(); + base.TearDown(); + } + + [Test] + public virtual void TestReverseDateSort() + { + IndexSearcher searcher = NewSearcher(Reader); + + Sort sort = new Sort(new SortField(DATE_TIME_FIELD, SortFieldType.STRING, true)); + Query query = new TermQuery(new Term(TEXT_FIELD, "document")); + + // Execute the search and process the search results. + string[] actualOrder = new string[5]; + ScoreDoc[] hits = searcher.Search(query, null, 1000, sort).ScoreDocs; + for (int i = 0; i < hits.Length; i++) + { + Document document = searcher.Doc(hits[i].Doc); + string text = document.Get(TEXT_FIELD); + actualOrder[i] = text; + } + + // Set up the expected order (i.e. Document 5, 4, 3, 2, 1). + string[] expectedOrder = new string[5]; + expectedOrder[0] = "Document 5"; + expectedOrder[1] = "Document 4"; + expectedOrder[2] = "Document 3"; + expectedOrder[3] = "Document 2"; + expectedOrder[4] = "Document 1"; + + Assert.AreEqual(Arrays.AsList(expectedOrder), Arrays.AsList(actualOrder)); + } + + private Document CreateDocument(string text, long time) + { + Document document = new Document(); + + // Add the text field. + Field textField = NewTextField(TEXT_FIELD, text, Field.Store.YES); + document.Add(textField); + + // Add the date/time field. + string dateTimeString = DateTools.TimeToString(time, DateTools.Resolution.SECOND); + Field dateTimeField = NewStringField(DATE_TIME_FIELD, dateTimeString, Field.Store.YES); + document.Add(dateTimeField); + + return document; + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestDisjunctionMaxQuery.cs ---------------------------------------------------------------------- diff --git a/src/Lucene.Net.Tests/Search/TestDisjunctionMaxQuery.cs b/src/Lucene.Net.Tests/Search/TestDisjunctionMaxQuery.cs new file mode 100644 index 0000000..18db902 --- /dev/null +++ b/src/Lucene.Net.Tests/Search/TestDisjunctionMaxQuery.cs @@ -0,0 +1,570 @@ +using System; +using System.Globalization; +using Lucene.Net.Documents; + +namespace Lucene.Net.Search +{ + using Lucene.Net.Index; + using NUnit.Framework; + using Analyzer = Lucene.Net.Analysis.Analyzer; + using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext; + using DefaultSimilarity = Lucene.Net.Search.Similarities.DefaultSimilarity; + using Directory = Lucene.Net.Store.Directory; + using DirectoryReader = Lucene.Net.Index.DirectoryReader; + using Document = Documents.Document; + + /* + * 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 Field = Field; + using FieldInvertState = Lucene.Net.Index.FieldInvertState; + using FieldType = FieldType; + using IndexReader = Lucene.Net.Index.IndexReader; + using IndexWriter = Lucene.Net.Index.IndexWriter; + using IndexWriterConfig = Lucene.Net.Index.IndexWriterConfig; + using LuceneTestCase = Lucene.Net.Util.LuceneTestCase; + using MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer; + using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter; + using Similarity = Lucene.Net.Search.Similarities.Similarity; + using SlowCompositeReaderWrapper = Lucene.Net.Index.SlowCompositeReaderWrapper; + using SpanQuery = Lucene.Net.Search.Spans.SpanQuery; + using SpanTermQuery = Lucene.Net.Search.Spans.SpanTermQuery; + using Term = Lucene.Net.Index.Term; + using TextField = TextField; + + /// <summary> + /// Test of the DisjunctionMaxQuery. + /// + /// </summary> + [TestFixture] + public class TestDisjunctionMaxQuery : LuceneTestCase + { + /// <summary> + /// threshold for comparing floats </summary> + public static readonly float SCORE_COMP_THRESH = 0.0000f; + + /// <summary> + /// Similarity to eliminate tf, idf and lengthNorm effects to isolate test + /// case. + /// + /// <p> + /// same as TestRankingSimilarity in TestRanking.zip from + /// http://issues.apache.org/jira/browse/LUCENE-323 + /// </p> + /// </summary> + private class TestSimilarity : DefaultSimilarity + { + public TestSimilarity() + { + } + + public override float Tf(float freq) + { + if (freq > 0.0f) + { + return 1.0f; + } + else + { + return 0.0f; + } + } + + public override float LengthNorm(FieldInvertState state) + { + // Disable length norm + return state.Boost; + } + + public override float Idf(long docFreq, long numDocs) + { + return 1.0f; + } + } + + public Similarity Sim = new TestSimilarity(); + public Directory Index; + public IndexReader r; + public IndexSearcher s; + + private static readonly FieldType NonAnalyzedType = new FieldType(TextField.TYPE_STORED); + + static TestDisjunctionMaxQuery() + { + NonAnalyzedType.IsTokenized = false; + } + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + Index = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), Index, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())).SetSimilarity(Sim).SetMergePolicy(NewLogMergePolicy())); + + // hed is the most important field, dek is secondary + + // d1 is an "ok" match for: albino elephant + { + Document d1 = new Document(); + d1.Add(NewField("id", "d1", NonAnalyzedType)); // Field.Keyword("id", + // "d1")); + d1.Add(NewTextField("hed", "elephant", Field.Store.YES)); // Field.Text("hed", "elephant")); + d1.Add(NewTextField("dek", "elephant", Field.Store.YES)); // Field.Text("dek", "elephant")); + writer.AddDocument(d1); + } + + // d2 is a "good" match for: albino elephant + { + Document d2 = new Document(); + d2.Add(NewField("id", "d2", NonAnalyzedType)); // Field.Keyword("id", + // "d2")); + d2.Add(NewTextField("hed", "elephant", Field.Store.YES)); // Field.Text("hed", "elephant")); + d2.Add(NewTextField("dek", "albino", Field.Store.YES)); // Field.Text("dek", + // "albino")); + d2.Add(NewTextField("dek", "elephant", Field.Store.YES)); // Field.Text("dek", "elephant")); + writer.AddDocument(d2); + } + + // d3 is a "better" match for: albino elephant + { + Document d3 = new Document(); + d3.Add(NewField("id", "d3", NonAnalyzedType)); // Field.Keyword("id", + // "d3")); + d3.Add(NewTextField("hed", "albino", Field.Store.YES)); // Field.Text("hed", + // "albino")); + d3.Add(NewTextField("hed", "elephant", Field.Store.YES)); // Field.Text("hed", "elephant")); + writer.AddDocument(d3); + } + + // d4 is the "best" match for: albino elephant + { + Document d4 = new Document(); + d4.Add(NewField("id", "d4", NonAnalyzedType)); // Field.Keyword("id", + // "d4")); + d4.Add(NewTextField("hed", "albino", Field.Store.YES)); // Field.Text("hed", + // "albino")); + d4.Add(NewField("hed", "elephant", NonAnalyzedType)); // Field.Text("hed", "elephant")); + d4.Add(NewTextField("dek", "albino", Field.Store.YES)); // Field.Text("dek", + // "albino")); + writer.AddDocument(d4); + } + + r = SlowCompositeReaderWrapper.Wrap(writer.Reader); + writer.Dispose(); + s = NewSearcher(r); + s.Similarity = Sim; + } + + [TearDown] + public override void TearDown() + { + r.Dispose(); + Index.Dispose(); + base.TearDown(); + } + + [Test] + public virtual void TestSkipToFirsttimeMiss() + { + DisjunctionMaxQuery dq = new DisjunctionMaxQuery(0.0f); + dq.Add(Tq("id", "d1")); + dq.Add(Tq("dek", "DOES_NOT_EXIST")); + + QueryUtils.Check(Random(), dq, s, Similarity); + Assert.IsTrue(s.TopReaderContext is AtomicReaderContext); + Weight dw = s.CreateNormalizedWeight(dq); + AtomicReaderContext context = (AtomicReaderContext)s.TopReaderContext; + Scorer ds = dw.GetScorer(context, (context.AtomicReader).LiveDocs); + bool skipOk = ds.Advance(3) != DocIdSetIterator.NO_MORE_DOCS; + if (skipOk) + { + Assert.Fail("firsttime skipTo found a match? ... " + r.Document(ds.DocID).Get("id")); + } + } + + [Test] + public virtual void TestSkipToFirsttimeHit() + { + DisjunctionMaxQuery dq = new DisjunctionMaxQuery(0.0f); + dq.Add(Tq("dek", "albino")); + dq.Add(Tq("dek", "DOES_NOT_EXIST")); + Assert.IsTrue(s.TopReaderContext is AtomicReaderContext); + QueryUtils.Check(Random(), dq, s, Similarity); + Weight dw = s.CreateNormalizedWeight(dq); + AtomicReaderContext context = (AtomicReaderContext)s.TopReaderContext; + Scorer ds = dw.GetScorer(context, (context.AtomicReader).LiveDocs); + Assert.IsTrue(ds.Advance(3) != DocIdSetIterator.NO_MORE_DOCS, "firsttime skipTo found no match"); + Assert.AreEqual("d4", r.Document(ds.DocID).Get("id"), "found wrong docid"); + } + + [Test] + public virtual void TestSimpleEqualScores1() + { + DisjunctionMaxQuery q = new DisjunctionMaxQuery(0.0f); + q.Add(Tq("hed", "albino")); + q.Add(Tq("hed", "elephant")); + QueryUtils.Check(Random(), q, s, Similarity); + + ScoreDoc[] h = s.Search(q, null, 1000).ScoreDocs; + + try + { + Assert.AreEqual(4, h.Length, "all docs should match " + q.ToString()); + + float score = h[0].Score; + for (int i = 1; i < h.Length; i++) + { + Assert.AreEqual(score, h[i].Score, SCORE_COMP_THRESH, "score #" + i + " is not the same"); + } + } + catch (Exception e) + { + PrintHits("testSimpleEqualScores1", h, s); + throw e; + } + } + + [Test] + public virtual void TestSimpleEqualScores2() + { + DisjunctionMaxQuery q = new DisjunctionMaxQuery(0.0f); + q.Add(Tq("dek", "albino")); + q.Add(Tq("dek", "elephant")); + QueryUtils.Check(Random(), q, s, Similarity); + + ScoreDoc[] h = s.Search(q, null, 1000).ScoreDocs; + + try + { + Assert.AreEqual(3, h.Length, "3 docs should match " + q.ToString()); + float score = h[0].Score; + for (int i = 1; i < h.Length; i++) + { + Assert.AreEqual(score, h[i].Score, SCORE_COMP_THRESH, "score #" + i + " is not the same"); + } + } + catch (Exception e) + { + PrintHits("testSimpleEqualScores2", h, s); + throw e; + } + } + + [Test] + public virtual void TestSimpleEqualScores3() + { + DisjunctionMaxQuery q = new DisjunctionMaxQuery(0.0f); + q.Add(Tq("hed", "albino")); + q.Add(Tq("hed", "elephant")); + q.Add(Tq("dek", "albino")); + q.Add(Tq("dek", "elephant")); + QueryUtils.Check(Random(), q, s, Similarity); + + ScoreDoc[] h = s.Search(q, null, 1000).ScoreDocs; + + try + { + Assert.AreEqual(4, h.Length, "all docs should match " + q.ToString()); + float score = h[0].Score; + for (int i = 1; i < h.Length; i++) + { + Assert.AreEqual(score, h[i].Score, SCORE_COMP_THRESH, "score #" + i + " is not the same"); + } + } + catch (Exception e) + { + PrintHits("testSimpleEqualScores3", h, s); + throw e; + } + } + + [Test] + public virtual void TestSimpleTiebreaker() + { + DisjunctionMaxQuery q = new DisjunctionMaxQuery(0.01f); + q.Add(Tq("dek", "albino")); + q.Add(Tq("dek", "elephant")); + QueryUtils.Check(Random(), q, s, Similarity); + + ScoreDoc[] h = s.Search(q, null, 1000).ScoreDocs; + + try + { + Assert.AreEqual(3, h.Length, "3 docs should match " + q.ToString()); + Assert.AreEqual("d2", s.Doc(h[0].Doc).Get("id"), "wrong first"); + float score0 = h[0].Score; + float score1 = h[1].Score; + float score2 = h[2].Score; + Assert.IsTrue(score0 > score1, "d2 does not have better score then others: " + score0 + " >? " + score1); + Assert.AreEqual(score1, score2, SCORE_COMP_THRESH, "d4 and d1 don't have equal scores"); + } + catch (Exception e) + { + PrintHits("testSimpleTiebreaker", h, s); + throw e; + } + } + + [Test] + public virtual void TestBooleanRequiredEqualScores() + { + BooleanQuery q = new BooleanQuery(); + { + DisjunctionMaxQuery q1 = new DisjunctionMaxQuery(0.0f); + q1.Add(Tq("hed", "albino")); + q1.Add(Tq("dek", "albino")); + q.Add(q1, Occur.MUST); // true,false); + QueryUtils.Check(Random(), q1, s, Similarity); + } + { + DisjunctionMaxQuery q2 = new DisjunctionMaxQuery(0.0f); + q2.Add(Tq("hed", "elephant")); + q2.Add(Tq("dek", "elephant")); + q.Add(q2, Occur.MUST); // true,false); + QueryUtils.Check(Random(), q2, s, Similarity); + } + + QueryUtils.Check(Random(), q, s, Similarity); + + ScoreDoc[] h = s.Search(q, null, 1000).ScoreDocs; + + try + { + Assert.AreEqual(3, h.Length, "3 docs should match " + q.ToString()); + float score = h[0].Score; + for (int i = 1; i < h.Length; i++) + { + Assert.AreEqual(score, h[i].Score, SCORE_COMP_THRESH, "score #" + i + " is not the same"); + } + } + catch (Exception e) + { + PrintHits("testBooleanRequiredEqualScores1", h, s); + throw e; + } + } + + [Test] + public virtual void TestBooleanOptionalNoTiebreaker() + { + BooleanQuery q = new BooleanQuery(); + { + DisjunctionMaxQuery q1 = new DisjunctionMaxQuery(0.0f); + q1.Add(Tq("hed", "albino")); + q1.Add(Tq("dek", "albino")); + q.Add(q1, Occur.SHOULD); // false,false); + } + { + DisjunctionMaxQuery q2 = new DisjunctionMaxQuery(0.0f); + q2.Add(Tq("hed", "elephant")); + q2.Add(Tq("dek", "elephant")); + q.Add(q2, Occur.SHOULD); // false,false); + } + QueryUtils.Check(Random(), q, s, Similarity); + + ScoreDoc[] h = s.Search(q, null, 1000).ScoreDocs; + + try + { + Assert.AreEqual(4, h.Length, "4 docs should match " + q.ToString()); + float score = h[0].Score; + for (int i = 1; i < h.Length - 1; i++) // note: -1 + { + Assert.AreEqual(score, h[i].Score, SCORE_COMP_THRESH, "score #" + i + " is not the same"); + } + Assert.AreEqual("d1", s.Doc(h[h.Length - 1].Doc).Get("id"), "wrong last"); + float score1 = h[h.Length - 1].Score; + Assert.IsTrue(score > score1, "d1 does not have worse score then others: " + score + " >? " + score1); + } + catch (Exception e) + { + PrintHits("testBooleanOptionalNoTiebreaker", h, s); + throw e; + } + } + + [Test] + public virtual void TestBooleanOptionalWithTiebreaker() + { + BooleanQuery q = new BooleanQuery(); + { + DisjunctionMaxQuery q1 = new DisjunctionMaxQuery(0.01f); + q1.Add(Tq("hed", "albino")); + q1.Add(Tq("dek", "albino")); + q.Add(q1, Occur.SHOULD); // false,false); + } + { + DisjunctionMaxQuery q2 = new DisjunctionMaxQuery(0.01f); + q2.Add(Tq("hed", "elephant")); + q2.Add(Tq("dek", "elephant")); + q.Add(q2, Occur.SHOULD); // false,false); + } + QueryUtils.Check(Random(), q, s, Similarity); + + ScoreDoc[] h = s.Search(q, null, 1000).ScoreDocs; + + try + { + Assert.AreEqual(4, h.Length, "4 docs should match " + q.ToString()); + + float score0 = h[0].Score; + float score1 = h[1].Score; + float score2 = h[2].Score; + float score3 = h[3].Score; + + string doc0 = s.Doc(h[0].Doc).Get("id"); + string doc1 = s.Doc(h[1].Doc).Get("id"); + string doc2 = s.Doc(h[2].Doc).Get("id"); + string doc3 = s.Doc(h[3].Doc).Get("id"); + + Assert.IsTrue(doc0.Equals("d2") || doc0.Equals("d4"), "doc0 should be d2 or d4: " + doc0); + Assert.IsTrue(doc1.Equals("d2") || doc1.Equals("d4"), "doc1 should be d2 or d4: " + doc0); + Assert.AreEqual(score0, score1, SCORE_COMP_THRESH, "score0 and score1 should match"); + Assert.AreEqual("d3", doc2, "wrong third"); + Assert.IsTrue(score1 > score2, "d3 does not have worse score then d2 and d4: " + score1 + " >? " + score2); + + Assert.AreEqual("d1", doc3, "wrong fourth"); + Assert.IsTrue(score2 > score3, "d1 does not have worse score then d3: " + score2 + " >? " + score3); + } + catch (Exception e) + { + PrintHits("testBooleanOptionalWithTiebreaker", h, s); + throw e; + } + } + + [Test] + public virtual void TestBooleanOptionalWithTiebreakerAndBoost() + { + BooleanQuery q = new BooleanQuery(); + { + DisjunctionMaxQuery q1 = new DisjunctionMaxQuery(0.01f); + q1.Add(Tq("hed", "albino", 1.5f)); + q1.Add(Tq("dek", "albino")); + q.Add(q1, Occur.SHOULD); // false,false); + } + { + DisjunctionMaxQuery q2 = new DisjunctionMaxQuery(0.01f); + q2.Add(Tq("hed", "elephant", 1.5f)); + q2.Add(Tq("dek", "elephant")); + q.Add(q2, Occur.SHOULD); // false,false); + } + QueryUtils.Check(Random(), q, s, Similarity); + + ScoreDoc[] h = s.Search(q, null, 1000).ScoreDocs; + + try + { + Assert.AreEqual(4, h.Length, "4 docs should match " + q.ToString()); + + float score0 = h[0].Score; + float score1 = h[1].Score; + float score2 = h[2].Score; + float score3 = h[3].Score; + + string doc0 = s.Doc(h[0].Doc).Get("id"); + string doc1 = s.Doc(h[1].Doc).Get("id"); + string doc2 = s.Doc(h[2].Doc).Get("id"); + string doc3 = s.Doc(h[3].Doc).Get("id"); + + Assert.AreEqual("d4", doc0, "doc0 should be d4: "); + Assert.AreEqual("d3", doc1, "doc1 should be d3: "); + Assert.AreEqual("d2", doc2, "doc2 should be d2: "); + Assert.AreEqual("d1", doc3, "doc3 should be d1: "); + + Assert.IsTrue(score0 > score1, "d4 does not have a better score then d3: " + score0 + " >? " + score1); + Assert.IsTrue(score1 > score2, "d3 does not have a better score then d2: " + score1 + " >? " + score2); + Assert.IsTrue(score2 > score3, "d3 does not have a better score then d1: " + score2 + " >? " + score3); + } + catch (Exception e) + { + PrintHits("TestBooleanOptionalWithTiebreakerAndBoost", h, s); + throw e; + } + } + + // LUCENE-4477 / LUCENE-4401: + [Test] + public virtual void TestBooleanSpanQuery() + { + int hits = 0; + Directory directory = NewDirectory(); + Analyzer indexerAnalyzer = new MockAnalyzer(Random()); + + IndexWriterConfig config = new IndexWriterConfig(TEST_VERSION_CURRENT, indexerAnalyzer); + IndexWriter writer = new IndexWriter(directory, config); + string FIELD = "content"; + Document d = new Document(); + d.Add(new TextField(FIELD, "clockwork orange", Field.Store.YES)); + writer.AddDocument(d); + writer.Dispose(); + + IndexReader indexReader = DirectoryReader.Open(directory); + IndexSearcher searcher = NewSearcher(indexReader); + + DisjunctionMaxQuery query = new DisjunctionMaxQuery(1.0f); + SpanQuery sq1 = new SpanTermQuery(new Term(FIELD, "clockwork")); + SpanQuery sq2 = new SpanTermQuery(new Term(FIELD, "clckwork")); + query.Add(sq1); + query.Add(sq2); + TopScoreDocCollector collector = TopScoreDocCollector.Create(1000, true); + searcher.Search(query, collector); + hits = collector.GetTopDocs().ScoreDocs.Length; + foreach (ScoreDoc scoreDoc in collector.GetTopDocs().ScoreDocs) + { + Console.WriteLine(scoreDoc.Doc); + } + indexReader.Dispose(); + Assert.AreEqual(hits, 1); + directory.Dispose(); + } + + /// <summary> + /// macro </summary> + protected internal virtual Query Tq(string f, string t) + { + return new TermQuery(new Term(f, t)); + } + + /// <summary> + /// macro </summary> + protected internal virtual Query Tq(string f, string t, float b) + { + Query q = Tq(f, t); + q.Boost = b; + return q; + } + + protected internal virtual void PrintHits(string test, ScoreDoc[] h, IndexSearcher searcher) + { + Console.Error.WriteLine("------- " + test + " -------"); + + //DecimalFormat f = new DecimalFormat("0.000000000", DecimalFormatSymbols.getInstance(Locale.ROOT)); + + NumberFormatInfo f = new NumberFormatInfo(); + f.NumberDecimalSeparator = "."; + + for (int i = 0; i < h.Length; i++) + { + Document d = searcher.Doc(h[i].Doc); + decimal score = (decimal)h[i].Score; + Console.Error.WriteLine("#" + i + ": " + score.ToString(f) + " - " + d.Get("id")); + } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestDocBoost.cs ---------------------------------------------------------------------- diff --git a/src/Lucene.Net.Tests/Search/TestDocBoost.cs b/src/Lucene.Net.Tests/Search/TestDocBoost.cs new file mode 100644 index 0000000..29fe7d6 --- /dev/null +++ b/src/Lucene.Net.Tests/Search/TestDocBoost.cs @@ -0,0 +1,122 @@ +using System; +using Lucene.Net.Documents; + +namespace Lucene.Net.Search +{ + + using NUnit.Framework; + using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext; + using Directory = Lucene.Net.Store.Directory; + using IndexReader = Lucene.Net.Index.IndexReader; + using LuceneTestCase = Lucene.Net.Util.LuceneTestCase; + + /* + * 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 MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer; + using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter; + using Term = Lucene.Net.Index.Term; + + /// <summary> + /// Document boost unit test. + /// + /// + /// </summary> + [TestFixture] + public class TestDocBoost : LuceneTestCase + { + [Test] + public virtual void TestDocBoost_Mem() + { + Directory store = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), store, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())).SetMergePolicy(NewLogMergePolicy())); + + Field f1 = NewTextField("field", "word", Field.Store.YES); + Field f2 = NewTextField("field", "word", Field.Store.YES); + f2.Boost = 2.0f; + + Documents.Document d1 = new Documents.Document(); + Documents.Document d2 = new Documents.Document(); + + d1.Add(f1); // boost = 1 + d2.Add(f2); // boost = 2 + + writer.AddDocument(d1); + writer.AddDocument(d2); + + IndexReader reader = writer.Reader; + writer.Dispose(); + + float[] scores = new float[4]; + + IndexSearcher searcher = NewSearcher(reader); + searcher.Search(new TermQuery(new Term("field", "word")), new CollectorAnonymousInnerClassHelper(this, scores)); + + float lastScore = 0.0f; + + for (int i = 0; i < 2; i++) + { + if (VERBOSE) + { + Console.WriteLine(searcher.Explain(new TermQuery(new Term("field", "word")), i)); + } + Assert.IsTrue(scores[i] > lastScore, "score: " + scores[i] + " should be > lastScore: " + lastScore); + lastScore = scores[i]; + } + + reader.Dispose(); + store.Dispose(); + } + + private class CollectorAnonymousInnerClassHelper : ICollector + { + private readonly TestDocBoost OuterInstance; + + private float[] Scores; + + public CollectorAnonymousInnerClassHelper(TestDocBoost outerInstance, float[] scores) + { + this.OuterInstance = outerInstance; + this.Scores = scores; + @base = 0; + } + + private int @base; + private Scorer scorer; + + public virtual void SetScorer(Scorer scorer) + { + this.scorer = scorer; + } + + public virtual void Collect(int doc) + { + Scores[doc + @base] = scorer.GetScore(); + } + + public virtual void SetNextReader(AtomicReaderContext context) + { + @base = context.DocBase; + } + + public virtual bool AcceptsDocsOutOfOrder + { + get { return true; } + } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestDocIdSet.cs ---------------------------------------------------------------------- diff --git a/src/Lucene.Net.Tests/Search/TestDocIdSet.cs b/src/Lucene.Net.Tests/Search/TestDocIdSet.cs new file mode 100644 index 0000000..440fe7d --- /dev/null +++ b/src/Lucene.Net.Tests/Search/TestDocIdSet.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using Lucene.Net.Documents; + +namespace Lucene.Net.Search +{ + using Lucene.Net.Support; + using NUnit.Framework; + using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext; + using IBits = Lucene.Net.Util.IBits; + using Directory = Lucene.Net.Store.Directory; + + /* + * 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 Document = Documents.Document; + using Field = Field; + using IndexReader = Lucene.Net.Index.IndexReader; + using LuceneTestCase = Lucene.Net.Util.LuceneTestCase; + using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter; + + [TestFixture] + public class TestDocIdSet : LuceneTestCase + { + [Test] + public virtual void TestFilteredDocIdSet() + { + const int maxdoc = 10; + DocIdSet innerSet = new DocIdSetAnonymousInnerClassHelper(this, maxdoc); + + DocIdSet filteredSet = new FilteredDocIdSetAnonymousInnerClassHelper(this, innerSet); + + DocIdSetIterator iter = filteredSet.GetIterator(); + List<int?> list = new List<int?>(); + int doc = iter.Advance(3); + if (doc != DocIdSetIterator.NO_MORE_DOCS) + { + list.Add(Convert.ToInt32(doc)); + while ((doc = iter.NextDoc()) != DocIdSetIterator.NO_MORE_DOCS) + { + list.Add(Convert.ToInt32(doc)); + } + } + + int[] docs = new int[list.Count]; + int c = 0; + IEnumerator<int?> intIter = list.GetEnumerator(); + while (intIter.MoveNext()) + { + docs[c++] = (int)intIter.Current; + } + int[] answer = new int[] { 4, 6, 8 }; + bool same = Arrays.Equals(answer, docs); + if (!same) + { + Console.WriteLine("answer: " + Arrays.ToString(answer)); + Console.WriteLine("gotten: " + Arrays.ToString(docs)); + Assert.Fail(); + } + } + + private class DocIdSetAnonymousInnerClassHelper : DocIdSet + { + private readonly TestDocIdSet OuterInstance; + + private int Maxdoc; + + public DocIdSetAnonymousInnerClassHelper(TestDocIdSet outerInstance, int maxdoc) + { + this.OuterInstance = outerInstance; + this.Maxdoc = maxdoc; + } + + public override DocIdSetIterator GetIterator() + { + return new DocIdSetIteratorAnonymousInnerClassHelper(this); + } + + private class DocIdSetIteratorAnonymousInnerClassHelper : DocIdSetIterator + { + private readonly DocIdSetAnonymousInnerClassHelper OuterInstance; + + public DocIdSetIteratorAnonymousInnerClassHelper(DocIdSetAnonymousInnerClassHelper outerInstance) + { + this.OuterInstance = outerInstance; + docid = -1; + } + + internal int docid; + + public override int DocID + { + get { return docid; } + } + + public override int NextDoc() + { + docid++; + return docid < OuterInstance.Maxdoc ? docid : (docid = NO_MORE_DOCS); + } + + public override int Advance(int target) + { + return SlowAdvance(target); + } + + public override long GetCost() + { + return 1; + } + } + } + + private class FilteredDocIdSetAnonymousInnerClassHelper : FilteredDocIdSet + { + private readonly TestDocIdSet OuterInstance; + + public FilteredDocIdSetAnonymousInnerClassHelper(TestDocIdSet outerInstance, DocIdSet innerSet) + : base(innerSet) + { + this.OuterInstance = outerInstance; + } + + protected override bool Match(int docid) + { + return docid % 2 == 0; //validate only even docids + } + } + + [Test] + public virtual void TestNullDocIdSet() + { + // Tests that if a Filter produces a null DocIdSet, which is given to + // IndexSearcher, everything works fine. this came up in LUCENE-1754. + Directory dir = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), dir, Similarity, TimeZone); + Document doc = new Document(); + doc.Add(NewStringField("c", "val", Field.Store.NO)); + writer.AddDocument(doc); + IndexReader reader = writer.Reader; + writer.Dispose(); + + // First verify the document is searchable. + IndexSearcher searcher = NewSearcher(reader); + Assert.AreEqual(1, searcher.Search(new MatchAllDocsQuery(), 10).TotalHits); + + // Now search w/ a Filter which returns a null DocIdSet + Filter f = new FilterAnonymousInnerClassHelper(this); + + Assert.AreEqual(0, searcher.Search(new MatchAllDocsQuery(), f, 10).TotalHits); + reader.Dispose(); + dir.Dispose(); + } + + private class FilterAnonymousInnerClassHelper : Filter + { + private readonly TestDocIdSet OuterInstance; + + public FilterAnonymousInnerClassHelper(TestDocIdSet outerInstance) + { + this.OuterInstance = outerInstance; + } + + public override DocIdSet GetDocIdSet(AtomicReaderContext context, IBits acceptDocs) + { + return null; + } + } + + [Test] + public virtual void TestNullIteratorFilteredDocIdSet() + { + Directory dir = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), dir, Similarity, TimeZone); + Document doc = new Document(); + doc.Add(NewStringField("c", "val", Field.Store.NO)); + writer.AddDocument(doc); + IndexReader reader = writer.Reader; + writer.Dispose(); + + // First verify the document is searchable. + IndexSearcher searcher = NewSearcher(reader); + Assert.AreEqual(1, searcher.Search(new MatchAllDocsQuery(), 10).TotalHits); + + // Now search w/ a Filter which returns a null DocIdSet + Filter f = new FilterAnonymousInnerClassHelper2(this); + + Assert.AreEqual(0, searcher.Search(new MatchAllDocsQuery(), f, 10).TotalHits); + reader.Dispose(); + dir.Dispose(); + } + + private class FilterAnonymousInnerClassHelper2 : Filter + { + private readonly TestDocIdSet OuterInstance; + + public FilterAnonymousInnerClassHelper2(TestDocIdSet outerInstance) + { + this.OuterInstance = outerInstance; + } + + public override DocIdSet GetDocIdSet(AtomicReaderContext context, IBits acceptDocs) + { + DocIdSet innerNullIteratorSet = new DocIdSetAnonymousInnerClassHelper2(this); + return new FilteredDocIdSetAnonymousInnerClassHelper2(this, innerNullIteratorSet); + } + + private class DocIdSetAnonymousInnerClassHelper2 : DocIdSet + { + private readonly FilterAnonymousInnerClassHelper2 OuterInstance; + + public DocIdSetAnonymousInnerClassHelper2(FilterAnonymousInnerClassHelper2 outerInstance) + { + this.OuterInstance = outerInstance; + } + + public override DocIdSetIterator GetIterator() + { + return null; + } + } + + private class FilteredDocIdSetAnonymousInnerClassHelper2 : FilteredDocIdSet + { + private readonly FilterAnonymousInnerClassHelper2 OuterInstance; + + public FilteredDocIdSetAnonymousInnerClassHelper2(FilterAnonymousInnerClassHelper2 outerInstance, DocIdSet innerNullIteratorSet) + : base(innerNullIteratorSet) + { + this.OuterInstance = outerInstance; + } + + protected override bool Match(int docid) + { + return true; + } + } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestDocTermOrdsRangeFilter.cs ---------------------------------------------------------------------- diff --git a/src/Lucene.Net.Tests/Search/TestDocTermOrdsRangeFilter.cs b/src/Lucene.Net.Tests/Search/TestDocTermOrdsRangeFilter.cs new file mode 100644 index 0000000..f6b2706 --- /dev/null +++ b/src/Lucene.Net.Tests/Search/TestDocTermOrdsRangeFilter.cs @@ -0,0 +1,149 @@ +using Lucene.Net.Documents; +using NUnit.Framework; +using System; +using System.Collections.Generic; + +namespace Lucene.Net.Search +{ + using Lucene.Net.Randomized.Generators; + using BytesRef = Lucene.Net.Util.BytesRef; + using Directory = Lucene.Net.Store.Directory; + using Document = Documents.Document; + using Field = Field; + using IndexReader = Lucene.Net.Index.IndexReader; + using LuceneTestCase = Lucene.Net.Util.LuceneTestCase; + + /* + * 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 MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer; + using MockTokenizer = Lucene.Net.Analysis.MockTokenizer; + using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter; + using SortedSetDocValuesField = SortedSetDocValuesField; + using Term = Lucene.Net.Index.Term; + using TestUtil = Lucene.Net.Util.TestUtil; + using UnicodeUtil = Lucene.Net.Util.UnicodeUtil; + + /// <summary> + /// Tests the DocTermOrdsRangeFilter + /// </summary> + [TestFixture] + public class TestDocTermOrdsRangeFilter : LuceneTestCase + { + protected internal IndexSearcher Searcher1; + protected internal IndexSearcher Searcher2; + private IndexReader Reader; + private Directory Dir; + protected internal string FieldName; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + Dir = NewDirectory(); + FieldName = Random().NextBoolean() ? "field" : ""; // sometimes use an empty string as field name + RandomIndexWriter writer = new RandomIndexWriter(Random(), Dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.KEYWORD, false)).SetMaxBufferedDocs(TestUtil.NextInt(Random(), 50, 1000))); + List<string> terms = new List<string>(); + int num = AtLeast(200); + for (int i = 0; i < num; i++) + { + Document doc = new Document(); + doc.Add(NewStringField("id", Convert.ToString(i), Field.Store.NO)); + int numTerms = Random().Next(4); + for (int j = 0; j < numTerms; j++) + { + string s = TestUtil.RandomUnicodeString(Random()); + doc.Add(NewStringField(FieldName, s, Field.Store.NO)); + // if the default codec doesn't support sortedset, we will uninvert at search time + if (DefaultCodecSupportsSortedSet()) + { + doc.Add(new SortedSetDocValuesField(FieldName, new BytesRef(s))); + } + terms.Add(s); + } + writer.AddDocument(doc); + } + + if (VERBOSE) + { + // utf16 order + terms.Sort(); + Console.WriteLine("UTF16 order:"); + foreach (string s in terms) + { + Console.WriteLine(" " + UnicodeUtil.ToHexString(s)); + } + } + + int numDeletions = Random().Next(num / 10); + for (int i = 0; i < numDeletions; i++) + { + writer.DeleteDocuments(new Term("id", Convert.ToString(Random().Next(num)))); + } + + Reader = writer.Reader; + Searcher1 = NewSearcher(Reader); + Searcher2 = NewSearcher(Reader); + writer.Dispose(); + } + + [TearDown] + public override void TearDown() + { + Reader.Dispose(); + Dir.Dispose(); + base.TearDown(); + } + + /// <summary> + /// test a bunch of random ranges </summary> + [Test] + public virtual void TestRanges() + { + int num = AtLeast(1000); + for (int i = 0; i < num; i++) + { + BytesRef lowerVal = new BytesRef(TestUtil.RandomUnicodeString(Random())); + BytesRef upperVal = new BytesRef(TestUtil.RandomUnicodeString(Random())); + if (upperVal.CompareTo(lowerVal) < 0) + { + AssertSame(upperVal, lowerVal, Random().NextBoolean(), Random().NextBoolean()); + } + else + { + AssertSame(lowerVal, upperVal, Random().NextBoolean(), Random().NextBoolean()); + } + } + } + + /// <summary> + /// check that the # of hits is the same as if the query + /// is run against the inverted index + /// </summary> + protected internal virtual void AssertSame(BytesRef lowerVal, BytesRef upperVal, bool includeLower, bool includeUpper) + { + Query docValues = new ConstantScoreQuery(DocTermOrdsRangeFilter.NewBytesRefRange(FieldName, lowerVal, upperVal, includeLower, includeUpper)); + MultiTermQuery inverted = new TermRangeQuery(FieldName, lowerVal, upperVal, includeLower, includeUpper); + inverted.MultiTermRewriteMethod = (MultiTermQuery.CONSTANT_SCORE_FILTER_REWRITE); + + TopDocs invertedDocs = Searcher1.Search(inverted, 25); + TopDocs docValuesDocs = Searcher2.Search(docValues, 25); + + CheckHits.CheckEqual(inverted, invertedDocs.ScoreDocs, docValuesDocs.ScoreDocs); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestDocTermOrdsRewriteMethod.cs ---------------------------------------------------------------------- diff --git a/src/Lucene.Net.Tests/Search/TestDocTermOrdsRewriteMethod.cs b/src/Lucene.Net.Tests/Search/TestDocTermOrdsRewriteMethod.cs new file mode 100644 index 0000000..7389e9e --- /dev/null +++ b/src/Lucene.Net.Tests/Search/TestDocTermOrdsRewriteMethod.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using Lucene.Net.Documents; + +namespace Lucene.Net.Search +{ + using Lucene.Net.Randomized.Generators; + using NUnit.Framework; + using AutomatonTestUtil = Lucene.Net.Util.Automaton.AutomatonTestUtil; + using BytesRef = Lucene.Net.Util.BytesRef; + using Directory = Lucene.Net.Store.Directory; + using Document = Documents.Document; + using Field = Field; + using IndexReader = Lucene.Net.Index.IndexReader; + using LuceneTestCase = Lucene.Net.Util.LuceneTestCase; + + /* + * 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 MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer; + using MockTokenizer = Lucene.Net.Analysis.MockTokenizer; + using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter; + using RegExp = Lucene.Net.Util.Automaton.RegExp; + using SortedSetDocValuesField = SortedSetDocValuesField; + using Term = Lucene.Net.Index.Term; + using TestUtil = Lucene.Net.Util.TestUtil; + using UnicodeUtil = Lucene.Net.Util.UnicodeUtil; + + /// <summary> + /// Tests the DocTermOrdsRewriteMethod + /// </summary> + [TestFixture] + public class TestDocTermOrdsRewriteMethod : LuceneTestCase + { + protected internal IndexSearcher Searcher1; + protected internal IndexSearcher Searcher2; + private IndexReader Reader; + private Directory Dir; + protected internal string FieldName; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + Dir = NewDirectory(); + FieldName = Random().NextBoolean() ? "field" : ""; // sometimes use an empty string as field name + RandomIndexWriter writer = new RandomIndexWriter(Random(), Dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random(), MockTokenizer.KEYWORD, false)).SetMaxBufferedDocs(TestUtil.NextInt(Random(), 50, 1000))); + List<string> terms = new List<string>(); + int num = AtLeast(200); + for (int i = 0; i < num; i++) + { + Document doc = new Document(); + doc.Add(NewStringField("id", Convert.ToString(i), Field.Store.NO)); + int numTerms = Random().Next(4); + for (int j = 0; j < numTerms; j++) + { + string s = TestUtil.RandomUnicodeString(Random()); + doc.Add(NewStringField(FieldName, s, Field.Store.NO)); + // if the default codec doesn't support sortedset, we will uninvert at search time + if (DefaultCodecSupportsSortedSet()) + { + doc.Add(new SortedSetDocValuesField(FieldName, new BytesRef(s))); + } + terms.Add(s); + } + writer.AddDocument(doc); + } + + if (VERBOSE) + { + // utf16 order + terms.Sort(); + Console.WriteLine("UTF16 order:"); + foreach (string s in terms) + { + Console.WriteLine(" " + UnicodeUtil.ToHexString(s)); + } + } + + int numDeletions = Random().Next(num / 10); + for (int i = 0; i < numDeletions; i++) + { + writer.DeleteDocuments(new Term("id", Convert.ToString(Random().Next(num)))); + } + + Reader = writer.Reader; + Searcher1 = NewSearcher(Reader); + Searcher2 = NewSearcher(Reader); + writer.Dispose(); + } + + [TearDown] + public override void TearDown() + { + Reader.Dispose(); + Dir.Dispose(); + base.TearDown(); + } + + /// <summary> + /// test a bunch of random regular expressions </summary> + [Test] + public virtual void TestRegexps() + { + int num = AtLeast(1000); + for (int i = 0; i < num; i++) + { + string reg = AutomatonTestUtil.RandomRegexp(Random()); + if (VERBOSE) + { + Console.WriteLine("TEST: regexp=" + reg); + } + AssertSame(reg); + } + } + + /// <summary> + /// check that the # of hits is the same as if the query + /// is run against the inverted index + /// </summary> + protected internal virtual void AssertSame(string regexp) + { + RegexpQuery docValues = new RegexpQuery(new Term(FieldName, regexp), RegExp.NONE); + docValues.MultiTermRewriteMethod = (new DocTermOrdsRewriteMethod()); + RegexpQuery inverted = new RegexpQuery(new Term(FieldName, regexp), RegExp.NONE); + + TopDocs invertedDocs = Searcher1.Search(inverted, 25); + TopDocs docValuesDocs = Searcher2.Search(docValues, 25); + + CheckHits.CheckEqual(inverted, invertedDocs.ScoreDocs, docValuesDocs.ScoreDocs); + } + + [Test] + public virtual void TestEquals() + { + RegexpQuery a1 = new RegexpQuery(new Term(FieldName, "[aA]"), RegExp.NONE); + RegexpQuery a2 = new RegexpQuery(new Term(FieldName, "[aA]"), RegExp.NONE); + RegexpQuery b = new RegexpQuery(new Term(FieldName, "[bB]"), RegExp.NONE); + Assert.AreEqual(a1, a2); + Assert.IsFalse(a1.Equals(b)); + + a1.MultiTermRewriteMethod = (new DocTermOrdsRewriteMethod()); + a2.MultiTermRewriteMethod = (new DocTermOrdsRewriteMethod()); + b.MultiTermRewriteMethod = (new DocTermOrdsRewriteMethod()); + Assert.AreEqual(a1, a2); + Assert.IsFalse(a1.Equals(b)); + QueryUtils.Check(a1); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestDocValuesScoring.cs ---------------------------------------------------------------------- diff --git a/src/Lucene.Net.Tests/Search/TestDocValuesScoring.cs b/src/Lucene.Net.Tests/Search/TestDocValuesScoring.cs new file mode 100644 index 0000000..ebd3e72 --- /dev/null +++ b/src/Lucene.Net.Tests/Search/TestDocValuesScoring.cs @@ -0,0 +1,233 @@ +using Lucene.Net.Documents; + +namespace Lucene.Net.Search +{ + using Lucene.Net.Index; + using NUnit.Framework; + using AtomicReaderContext = Lucene.Net.Index.AtomicReaderContext; + using BytesRef = Lucene.Net.Util.BytesRef; + using Directory = Lucene.Net.Store.Directory; + + /* + * 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 Document = Documents.Document; + using Field = Field; + using FieldInvertState = Lucene.Net.Index.FieldInvertState; + using SingleDocValuesField = SingleDocValuesField; + using IndexReader = Lucene.Net.Index.IndexReader; + using LuceneTestCase = Lucene.Net.Util.LuceneTestCase; + using PerFieldSimilarityWrapper = Lucene.Net.Search.Similarities.PerFieldSimilarityWrapper; + using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter; + using Similarity = Lucene.Net.Search.Similarities.Similarity; + using Term = Lucene.Net.Index.Term; + + /// <summary> + /// Tests the use of indexdocvalues in scoring. + /// + /// In the example, a docvalues field is used as a per-document boost (separate from the norm) + /// @lucene.experimental + /// </summary> + [SuppressCodecs("Lucene3x")] + [TestFixture] + public class TestDocValuesScoring : LuceneTestCase + { + private const float SCORE_EPSILON = 0.001f; // for comparing floats + + [Test] + public virtual void TestSimple() + { + Directory dir = NewDirectory(); + RandomIndexWriter iw = new RandomIndexWriter(Random(), dir, Similarity, TimeZone); + Document doc = new Document(); + Field field = NewTextField("foo", "", Field.Store.NO); + doc.Add(field); + Field dvField = new SingleDocValuesField("foo_boost", 0.0F); + doc.Add(dvField); + Field field2 = NewTextField("bar", "", Field.Store.NO); + doc.Add(field2); + + field.SetStringValue("quick brown fox"); + field2.SetStringValue("quick brown fox"); + dvField.SetSingleValue(2f); // boost x2 + iw.AddDocument(doc); + field.SetStringValue("jumps over lazy brown dog"); + field2.SetStringValue("jumps over lazy brown dog"); + dvField.SetSingleValue(4f); // boost x4 + iw.AddDocument(doc); + IndexReader ir = iw.Reader; + iw.Dispose(); + + // no boosting + IndexSearcher searcher1 = NewSearcher(ir, false, Similarity); + Similarity @base = searcher1.Similarity; + // boosting + IndexSearcher searcher2 = NewSearcher(ir, false, Similarity); + searcher2.Similarity = new PerFieldSimilarityWrapperAnonymousInnerClassHelper(this, field, @base); + + // in this case, we searched on field "foo". first document should have 2x the score. + TermQuery tq = new TermQuery(new Term("foo", "quick")); + QueryUtils.Check(Random(), tq, searcher1, Similarity); + QueryUtils.Check(Random(), tq, searcher2, Similarity); + + TopDocs noboost = searcher1.Search(tq, 10); + TopDocs boost = searcher2.Search(tq, 10); + Assert.AreEqual(1, noboost.TotalHits); + Assert.AreEqual(1, boost.TotalHits); + + //System.out.println(searcher2.Explain(tq, boost.ScoreDocs[0].Doc)); + Assert.AreEqual(boost.ScoreDocs[0].Score, noboost.ScoreDocs[0].Score * 2f, SCORE_EPSILON); + + // this query matches only the second document, which should have 4x the score. + tq = new TermQuery(new Term("foo", "jumps")); + QueryUtils.Check(Random(), tq, searcher1, Similarity); + QueryUtils.Check(Random(), tq, searcher2, Similarity); + + noboost = searcher1.Search(tq, 10); + boost = searcher2.Search(tq, 10); + Assert.AreEqual(1, noboost.TotalHits); + Assert.AreEqual(1, boost.TotalHits); + + Assert.AreEqual(boost.ScoreDocs[0].Score, noboost.ScoreDocs[0].Score * 4f, SCORE_EPSILON); + + // search on on field bar just for kicks, nothing should happen, since we setup + // our sim provider to only use foo_boost for field foo. + tq = new TermQuery(new Term("bar", "quick")); + QueryUtils.Check(Random(), tq, searcher1, Similarity); + QueryUtils.Check(Random(), tq, searcher2, Similarity); + + noboost = searcher1.Search(tq, 10); + boost = searcher2.Search(tq, 10); + Assert.AreEqual(1, noboost.TotalHits); + Assert.AreEqual(1, boost.TotalHits); + + Assert.AreEqual(boost.ScoreDocs[0].Score, noboost.ScoreDocs[0].Score, SCORE_EPSILON); + + ir.Dispose(); + dir.Dispose(); + } + + private class PerFieldSimilarityWrapperAnonymousInnerClassHelper : PerFieldSimilarityWrapper + { + private readonly TestDocValuesScoring OuterInstance; + + private Field Field; + private Similarity @base; + + public PerFieldSimilarityWrapperAnonymousInnerClassHelper(TestDocValuesScoring outerInstance, Field field, Similarity @base) + { + this.OuterInstance = outerInstance; + this.Field = field; + this.@base = @base; + fooSim = new BoostingSimilarity(@base, "foo_boost"); + } + + internal readonly Similarity fooSim; + + public override Similarity Get(string field) + { + return "foo".Equals(field) ? fooSim : @base; + } + + public override float Coord(int overlap, int maxOverlap) + { + return @base.Coord(overlap, maxOverlap); + } + + public override float QueryNorm(float sumOfSquaredWeights) + { + return @base.QueryNorm(sumOfSquaredWeights); + } + } + + /// <summary> + /// Similarity that wraps another similarity and boosts the final score + /// according to whats in a docvalues field. + /// + /// @lucene.experimental + /// </summary> + internal class BoostingSimilarity : Similarity + { + internal readonly Similarity Sim; + internal readonly string BoostField; + + public BoostingSimilarity(Similarity sim, string boostField) + { + this.Sim = sim; + this.BoostField = boostField; + } + + public override long ComputeNorm(FieldInvertState state) + { + return Sim.ComputeNorm(state); + } + + public override SimWeight ComputeWeight(float queryBoost, CollectionStatistics collectionStats, params TermStatistics[] termStats) + { + return Sim.ComputeWeight(queryBoost, collectionStats, termStats); + } + + public override SimScorer GetSimScorer(SimWeight stats, AtomicReaderContext context) + { + SimScorer sub = Sim.GetSimScorer(stats, context); + FieldCache.Singles values = FieldCache.DEFAULT.GetSingles(context.AtomicReader, BoostField, false); + + return new SimScorerAnonymousInnerClassHelper(this, sub, values); + } + + private class SimScorerAnonymousInnerClassHelper : SimScorer + { + private readonly BoostingSimilarity OuterInstance; + + private SimScorer Sub; + private FieldCache.Singles Values; + + public SimScorerAnonymousInnerClassHelper(BoostingSimilarity outerInstance, SimScorer sub, FieldCache.Singles values) + { + this.OuterInstance = outerInstance; + this.Sub = sub; + this.Values = values; + } + + public override float Score(int doc, float freq) + { + return Values.Get(doc) * Sub.Score(doc, freq); + } + + public override float ComputeSlopFactor(int distance) + { + return Sub.ComputeSlopFactor(distance); + } + + public override float ComputePayloadFactor(int doc, int start, int end, BytesRef payload) + { + return Sub.ComputePayloadFactor(doc, start, end, payload); + } + + public override Explanation Explain(int doc, Explanation freq) + { + Explanation boostExplanation = new Explanation(Values.Get(doc), "indexDocValue(" + OuterInstance.BoostField + ")"); + Explanation simExplanation = Sub.Explain(doc, freq); + Explanation expl = new Explanation(boostExplanation.Value * simExplanation.Value, "product of:"); + expl.AddDetail(boostExplanation); + expl.AddDetail(simExplanation); + return expl; + } + } + } + } +} \ No newline at end of file
