http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestFuzzyQuery.cs ---------------------------------------------------------------------- diff --git a/src/Lucene.Net.Tests/Search/TestFuzzyQuery.cs b/src/Lucene.Net.Tests/Search/TestFuzzyQuery.cs new file mode 100644 index 0000000..0ead1d4 --- /dev/null +++ b/src/Lucene.Net.Tests/Search/TestFuzzyQuery.cs @@ -0,0 +1,389 @@ +using System.Collections.Generic; +using Lucene.Net.Documents; +using Lucene.Net.Util; + +namespace Lucene.Net.Search +{ + using Lucene.Net.Support; + using NUnit.Framework; + 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 MultiReader = Lucene.Net.Index.MultiReader; + using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter; + using Term = Lucene.Net.Index.Term; + + /// <summary> + /// Tests <seealso cref="FuzzyQuery"/>. + /// + /// </summary> + [TestFixture] + public class TestFuzzyQuery : LuceneTestCaseWithReducedFloatPrecision + { + [Test] + public virtual void TestFuzziness() + { + Directory directory = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), directory, Similarity, TimeZone); + AddDoc("aaaaa", writer); + AddDoc("aaaab", writer); + AddDoc("aaabb", writer); + AddDoc("aabbb", writer); + AddDoc("abbbb", writer); + AddDoc("bbbbb", writer); + AddDoc("ddddd", writer); + + IndexReader reader = writer.Reader; + IndexSearcher searcher = NewSearcher(reader); + writer.Dispose(); + + FuzzyQuery query = new FuzzyQuery(new Term("field", "aaaaa"), FuzzyQuery.DefaultMaxEdits, 0); + ScoreDoc[] hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(3, hits.Length); + + // same with prefix + query = new FuzzyQuery(new Term("field", "aaaaa"), FuzzyQuery.DefaultMaxEdits, 1); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(3, hits.Length); + query = new FuzzyQuery(new Term("field", "aaaaa"), FuzzyQuery.DefaultMaxEdits, 2); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(3, hits.Length); + query = new FuzzyQuery(new Term("field", "aaaaa"), FuzzyQuery.DefaultMaxEdits, 3); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(3, hits.Length); + query = new FuzzyQuery(new Term("field", "aaaaa"), FuzzyQuery.DefaultMaxEdits, 4); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(2, hits.Length); + query = new FuzzyQuery(new Term("field", "aaaaa"), FuzzyQuery.DefaultMaxEdits, 5); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(1, hits.Length); + query = new FuzzyQuery(new Term("field", "aaaaa"), FuzzyQuery.DefaultMaxEdits, 6); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(1, hits.Length); + + // test scoring + query = new FuzzyQuery(new Term("field", "bbbbb"), FuzzyQuery.DefaultMaxEdits, 0); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(3, hits.Length, "3 documents should match"); + IList<string> order = Arrays.AsList("bbbbb", "abbbb", "aabbb"); + for (int i = 0; i < hits.Length; i++) + { + string term = searcher.Doc(hits[i].Doc).Get("field"); + //System.out.println(hits[i].Score); + Assert.AreEqual(order[i], term); + } + + // test pq size by supplying maxExpansions=2 + // this query would normally return 3 documents, because 3 terms match (see above): + query = new FuzzyQuery(new Term("field", "bbbbb"), FuzzyQuery.DefaultMaxEdits, 0, 2, false); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(2, hits.Length, "only 2 documents should match"); + order = Arrays.AsList("bbbbb", "abbbb"); + for (int i = 0; i < hits.Length; i++) + { + string term = searcher.Doc(hits[i].Doc).Get("field"); + //System.out.println(hits[i].Score); + Assert.AreEqual(order[i], term); + } + + // not similar enough: + query = new FuzzyQuery(new Term("field", "xxxxx"), FuzzyQuery.DefaultMaxEdits, 0); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(0, hits.Length); + query = new FuzzyQuery(new Term("field", "aaccc"), FuzzyQuery.DefaultMaxEdits, 0); // edit distance to "aaaaa" = 3 + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(0, hits.Length); + + // query identical to a word in the index: + query = new FuzzyQuery(new Term("field", "aaaaa"), FuzzyQuery.DefaultMaxEdits, 0); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(3, hits.Length); + Assert.AreEqual(searcher.Doc(hits[0].Doc).Get("field"), ("aaaaa")); + // default allows for up to two edits: + Assert.AreEqual(searcher.Doc(hits[1].Doc).Get("field"), ("aaaab")); + Assert.AreEqual(searcher.Doc(hits[2].Doc).Get("field"), ("aaabb")); + + // query similar to a word in the index: + query = new FuzzyQuery(new Term("field", "aaaac"), FuzzyQuery.DefaultMaxEdits, 0); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(3, hits.Length); + Assert.AreEqual(searcher.Doc(hits[0].Doc).Get("field"), ("aaaaa")); + Assert.AreEqual(searcher.Doc(hits[1].Doc).Get("field"), ("aaaab")); + Assert.AreEqual(searcher.Doc(hits[2].Doc).Get("field"), ("aaabb")); + + // now with prefix + query = new FuzzyQuery(new Term("field", "aaaac"), FuzzyQuery.DefaultMaxEdits, 1); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(3, hits.Length); + Assert.AreEqual(searcher.Doc(hits[0].Doc).Get("field"), ("aaaaa")); + Assert.AreEqual(searcher.Doc(hits[1].Doc).Get("field"), ("aaaab")); + Assert.AreEqual(searcher.Doc(hits[2].Doc).Get("field"), ("aaabb")); + query = new FuzzyQuery(new Term("field", "aaaac"), FuzzyQuery.DefaultMaxEdits, 2); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(3, hits.Length); + Assert.AreEqual(searcher.Doc(hits[0].Doc).Get("field"), ("aaaaa")); + Assert.AreEqual(searcher.Doc(hits[1].Doc).Get("field"), ("aaaab")); + Assert.AreEqual(searcher.Doc(hits[2].Doc).Get("field"), ("aaabb")); + query = new FuzzyQuery(new Term("field", "aaaac"), FuzzyQuery.DefaultMaxEdits, 3); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(3, hits.Length); + Assert.AreEqual(searcher.Doc(hits[0].Doc).Get("field"), ("aaaaa")); + Assert.AreEqual(searcher.Doc(hits[1].Doc).Get("field"), ("aaaab")); + Assert.AreEqual(searcher.Doc(hits[2].Doc).Get("field"), ("aaabb")); + query = new FuzzyQuery(new Term("field", "aaaac"), FuzzyQuery.DefaultMaxEdits, 4); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(2, hits.Length); + Assert.AreEqual(searcher.Doc(hits[0].Doc).Get("field"), ("aaaaa")); + Assert.AreEqual(searcher.Doc(hits[1].Doc).Get("field"), ("aaaab")); + query = new FuzzyQuery(new Term("field", "aaaac"), FuzzyQuery.DefaultMaxEdits, 5); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(0, hits.Length); + + query = new FuzzyQuery(new Term("field", "ddddX"), FuzzyQuery.DefaultMaxEdits, 0); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(1, hits.Length); + Assert.AreEqual(searcher.Doc(hits[0].Doc).Get("field"), ("ddddd")); + + // now with prefix + query = new FuzzyQuery(new Term("field", "ddddX"), FuzzyQuery.DefaultMaxEdits, 1); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(1, hits.Length); + Assert.AreEqual(searcher.Doc(hits[0].Doc).Get("field"), ("ddddd")); + query = new FuzzyQuery(new Term("field", "ddddX"), FuzzyQuery.DefaultMaxEdits, 2); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(1, hits.Length); + Assert.AreEqual(searcher.Doc(hits[0].Doc).Get("field"), ("ddddd")); + query = new FuzzyQuery(new Term("field", "ddddX"), FuzzyQuery.DefaultMaxEdits, 3); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(1, hits.Length); + Assert.AreEqual(searcher.Doc(hits[0].Doc).Get("field"), ("ddddd")); + query = new FuzzyQuery(new Term("field", "ddddX"), FuzzyQuery.DefaultMaxEdits, 4); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(1, hits.Length); + Assert.AreEqual(searcher.Doc(hits[0].Doc).Get("field"), ("ddddd")); + query = new FuzzyQuery(new Term("field", "ddddX"), FuzzyQuery.DefaultMaxEdits, 5); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(0, hits.Length); + + // different field = no match: + query = new FuzzyQuery(new Term("anotherfield", "ddddX"), FuzzyQuery.DefaultMaxEdits, 0); + hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(0, hits.Length); + + reader.Dispose(); + directory.Dispose(); + } + + [Test] + public virtual void Test2() + { + Directory directory = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), directory, new MockAnalyzer(Random(), MockTokenizer.KEYWORD, false), Similarity, TimeZone); + AddDoc("LANGE", writer); + AddDoc("LUETH", writer); + AddDoc("PIRSING", writer); + AddDoc("RIEGEL", writer); + AddDoc("TRZECZIAK", writer); + AddDoc("WALKER", writer); + AddDoc("WBR", writer); + AddDoc("WE", writer); + AddDoc("WEB", writer); + AddDoc("WEBE", writer); + AddDoc("WEBER", writer); + AddDoc("WEBERE", writer); + AddDoc("WEBREE", writer); + AddDoc("WEBEREI", writer); + AddDoc("WBRE", writer); + AddDoc("WITTKOPF", writer); + AddDoc("WOJNAROWSKI", writer); + AddDoc("WRICKE", writer); + + IndexReader reader = writer.Reader; + IndexSearcher searcher = NewSearcher(reader); + writer.Dispose(); + + FuzzyQuery query = new FuzzyQuery(new Term("field", "WEBER"), 2, 1); + //query.setRewriteMethod(FuzzyQuery.SCORING_BOOLEAN_QUERY_REWRITE); + ScoreDoc[] hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(8, hits.Length); + + reader.Dispose(); + directory.Dispose(); + } + + /// <summary> + /// MultiTermQuery provides (via attribute) information about which values + /// must be competitive to enter the priority queue. + /// + /// FuzzyQuery optimizes itself around this information, if the attribute + /// is not implemented correctly, there will be problems! + /// </summary> + [Test] + public virtual void TestTieBreaker() + { + Directory directory = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), directory, Similarity, TimeZone); + AddDoc("a123456", writer); + AddDoc("c123456", writer); + AddDoc("d123456", writer); + AddDoc("e123456", writer); + + Directory directory2 = NewDirectory(); + RandomIndexWriter writer2 = new RandomIndexWriter(Random(), directory2, Similarity, TimeZone); + AddDoc("a123456", writer2); + AddDoc("b123456", writer2); + AddDoc("b123456", writer2); + AddDoc("b123456", writer2); + AddDoc("c123456", writer2); + AddDoc("f123456", writer2); + + IndexReader ir1 = writer.Reader; + IndexReader ir2 = writer2.Reader; + + MultiReader mr = new MultiReader(ir1, ir2); + IndexSearcher searcher = NewSearcher(mr); + FuzzyQuery fq = new FuzzyQuery(new Term("field", "z123456"), 1, 0, 2, false); + TopDocs docs = searcher.Search(fq, 2); + Assert.AreEqual(5, docs.TotalHits); // 5 docs, from the a and b's + mr.Dispose(); + ir1.Dispose(); + ir2.Dispose(); + writer.Dispose(); + writer2.Dispose(); + directory.Dispose(); + directory2.Dispose(); + } + + /// <summary> + /// Test the TopTermsBoostOnlyBooleanQueryRewrite rewrite method. </summary> + [Test] + public virtual void TestBoostOnlyRewrite() + { + Directory directory = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), directory, Similarity, TimeZone); + AddDoc("Lucene", writer); + AddDoc("Lucene", writer); + AddDoc("Lucenne", writer); + + IndexReader reader = writer.Reader; + IndexSearcher searcher = NewSearcher(reader); + writer.Dispose(); + + FuzzyQuery query = new FuzzyQuery(new Term("field", "lucene")); + query.MultiTermRewriteMethod = (new MultiTermQuery.TopTermsBoostOnlyBooleanQueryRewrite(50)); + ScoreDoc[] hits = searcher.Search(query, null, 1000).ScoreDocs; + Assert.AreEqual(3, hits.Length); + // normally, 'Lucenne' would be the first result as IDF will skew the score. + Assert.AreEqual("Lucene", reader.Document(hits[0].Doc).Get("field")); + Assert.AreEqual("Lucene", reader.Document(hits[1].Doc).Get("field")); + Assert.AreEqual("Lucenne", reader.Document(hits[2].Doc).Get("field")); + reader.Dispose(); + directory.Dispose(); + } + + [Test] + public virtual void TestGiga() + { + MockAnalyzer analyzer = new MockAnalyzer(Random()); + Directory index = NewDirectory(); + RandomIndexWriter w = new RandomIndexWriter(Random(), index, Similarity, TimeZone); + + AddDoc("Lucene in Action", w); + AddDoc("Lucene for Dummies", w); + + //addDoc("Giga", w); + AddDoc("Giga byte", w); + + AddDoc("ManagingGigabytesManagingGigabyte", w); + AddDoc("ManagingGigabytesManagingGigabytes", w); + + AddDoc("The Art of Computer Science", w); + AddDoc("J. K. Rowling", w); + AddDoc("JK Rowling", w); + AddDoc("Joanne K Roling", w); + AddDoc("Bruce Willis", w); + AddDoc("Willis bruce", w); + AddDoc("Brute willis", w); + AddDoc("B. willis", w); + IndexReader r = w.Reader; + w.Dispose(); + + Query q = new FuzzyQuery(new Term("field", "giga"), 0); + + // 3. search + IndexSearcher searcher = NewSearcher(r); + ScoreDoc[] hits = searcher.Search(q, 10).ScoreDocs; + Assert.AreEqual(1, hits.Length); + Assert.AreEqual("Giga byte", searcher.Doc(hits[0].Doc).Get("field")); + r.Dispose(); + index.Dispose(); + } + + [Test] + public virtual void TestDistanceAsEditsSearching() + { + Directory index = NewDirectory(); + RandomIndexWriter w = new RandomIndexWriter(Random(), index, Similarity, TimeZone); + AddDoc("foobar", w); + AddDoc("test", w); + AddDoc("working", w); + IndexReader reader = w.Reader; + IndexSearcher searcher = NewSearcher(reader); + w.Dispose(); + + FuzzyQuery q = new FuzzyQuery(new Term("field", "fouba"), 2); + ScoreDoc[] hits = searcher.Search(q, 10).ScoreDocs; + Assert.AreEqual(1, hits.Length); + Assert.AreEqual("foobar", searcher.Doc(hits[0].Doc).Get("field")); + + q = new FuzzyQuery(new Term("field", "foubara"), 2); + hits = searcher.Search(q, 10).ScoreDocs; + Assert.AreEqual(1, hits.Length); + Assert.AreEqual("foobar", searcher.Doc(hits[0].Doc).Get("field")); + + try + { + q = new FuzzyQuery(new Term("field", "t"), 3); + Assert.Fail(); + } +#pragma warning disable 168 + catch (System.ArgumentException expected) +#pragma warning restore 168 + { + // expected + } + + reader.Dispose(); + index.Dispose(); + } + + private void AddDoc(string text, RandomIndexWriter writer) + { + Document doc = new Document(); + doc.Add(NewTextField("field", text, Field.Store.YES)); + writer.AddDocument(doc); + } + } +} \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestIndexSearcher.cs ---------------------------------------------------------------------- diff --git a/src/Lucene.Net.Tests/Search/TestIndexSearcher.cs b/src/Lucene.Net.Tests/Search/TestIndexSearcher.cs new file mode 100644 index 0000000..d5dce40 --- /dev/null +++ b/src/Lucene.Net.Tests/Search/TestIndexSearcher.cs @@ -0,0 +1,145 @@ +using Lucene.Net.Documents; +using Lucene.Net.Index; +using Lucene.Net.Support; +using Lucene.Net.Util; +using NUnit.Framework; +using System; +using System.Threading.Tasks; + +namespace Lucene.Net.Search +{ + 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 IOUtils = Lucene.Net.Util.IOUtils; + using LuceneTestCase = Lucene.Net.Util.LuceneTestCase; + using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter; + + [TestFixture] + public class TestIndexSearcher : LuceneTestCase + { + internal Directory Dir; + internal IndexReader Reader; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + Dir = NewDirectory(); + RandomIndexWriter iw = new RandomIndexWriter(Random(), Dir, Similarity, TimeZone); + for (int i = 0; i < 100; i++) + { + Document doc = new Document(); + doc.Add(NewStringField("field", Convert.ToString(i), Field.Store.NO)); + doc.Add(NewStringField("field2", Convert.ToString(i % 2 == 0), Field.Store.NO)); + iw.AddDocument(doc); + } + Reader = iw.Reader; + iw.Dispose(); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + Reader.Dispose(); + Dir.Dispose(); + } + + // should not throw exception + [Test] + public virtual void TestHugeN() + { + TaskScheduler service = new LimitedConcurrencyLevelTaskScheduler(4); + + IndexSearcher[] searchers = new IndexSearcher[] { new IndexSearcher(Reader), new IndexSearcher(Reader, service) }; + Query[] queries = new Query[] { new MatchAllDocsQuery(), new TermQuery(new Term("field", "1")) }; + Sort[] sorts = new Sort[] { null, new Sort(new SortField("field2", SortFieldType.STRING)) }; + Filter[] filters = new Filter[] { null, new QueryWrapperFilter(new TermQuery(new Term("field2", "true"))) }; + ScoreDoc[] afters = new ScoreDoc[] { null, new FieldDoc(0, 0f, new object[] { new BytesRef("boo!") }) }; + + foreach (IndexSearcher searcher in searchers) + { + foreach (ScoreDoc after in afters) + { + foreach (Query query in queries) + { + foreach (Sort sort in sorts) + { + foreach (Filter filter in filters) + { + searcher.Search(query, int.MaxValue); + searcher.SearchAfter(after, query, int.MaxValue); + searcher.Search(query, filter, int.MaxValue); + searcher.SearchAfter(after, query, filter, int.MaxValue); + if (sort != null) + { + searcher.Search(query, int.MaxValue, sort); + searcher.Search(query, filter, int.MaxValue, sort); + searcher.Search(query, filter, int.MaxValue, sort, true, true); + searcher.Search(query, filter, int.MaxValue, sort, true, false); + searcher.Search(query, filter, int.MaxValue, sort, false, true); + searcher.Search(query, filter, int.MaxValue, sort, false, false); + searcher.SearchAfter(after, query, filter, int.MaxValue, sort); + searcher.SearchAfter(after, query, filter, int.MaxValue, sort, true, true); + searcher.SearchAfter(after, query, filter, int.MaxValue, sort, true, false); + searcher.SearchAfter(after, query, filter, int.MaxValue, sort, false, true); + searcher.SearchAfter(after, query, filter, int.MaxValue, sort, false, false); + } + } + } + } + } + } + + TestUtil.ShutdownExecutorService(service); + } + + [Test] + public virtual void TestSearchAfterPassedMaxDoc() + { + // LUCENE-5128: ensure we get a meaningful message if searchAfter exceeds maxDoc + Directory dir = NewDirectory(); + RandomIndexWriter w = new RandomIndexWriter(Random(), dir, Similarity, TimeZone); + w.AddDocument(new Document()); + IndexReader r = w.Reader; + w.Dispose(); + + IndexSearcher s = new IndexSearcher(r); + try + { + s.SearchAfter(new ScoreDoc(r.MaxDoc, 0.54f), new MatchAllDocsQuery(), 10); + Assert.Fail("should have hit IllegalArgumentException when searchAfter exceeds maxDoc"); + } +#pragma warning disable 168 + catch (System.ArgumentException e) +#pragma warning restore 168 + { + // ok + } + finally + { + IOUtils.Close(r, dir); + } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestLiveFieldValues.cs ---------------------------------------------------------------------- diff --git a/src/Lucene.Net.Tests/Search/TestLiveFieldValues.cs b/src/Lucene.Net.Tests/Search/TestLiveFieldValues.cs new file mode 100644 index 0000000..9cd902c --- /dev/null +++ b/src/Lucene.Net.Tests/Search/TestLiveFieldValues.cs @@ -0,0 +1,245 @@ +using Lucene.Net.Documents; +using Lucene.Net.Support; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading; + +namespace Lucene.Net.Search +{ + using Index; + using NUnit.Framework; + using Directory = Lucene.Net.Store.Directory; + using Document = Documents.Document; + using Field = Field; + using IndexReader = Lucene.Net.Index.IndexReader; + using IndexWriter = Lucene.Net.Index.IndexWriter; + using IndexWriterConfig = Lucene.Net.Index.IndexWriterConfig; + using Int32Field = Int32Field; + 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 StringField = StringField; + using Term = Lucene.Net.Index.Term; + using TestUtil = Lucene.Net.Util.TestUtil; + + [TestFixture] + public class TestLiveFieldValues : LuceneTestCase + { + [Test] + public virtual void Test() + { + Directory dir = NewFSDirectory(CreateTempDir("livefieldupdates")); + IndexWriterConfig iwc = NewIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random())); + + IndexWriter w = new IndexWriter(dir, iwc); + + SearcherManager mgr = new SearcherManager(w, true, new SearcherFactoryAnonymousInnerClassHelper()); + + const int missing = -1; + + LiveFieldValues<IndexSearcher, int?> rt = new LiveFieldValuesAnonymousInnerClassHelper(mgr, missing); + + int numThreads = TestUtil.NextInt(Random(), 2, 5); + if (VERBOSE) + { + Console.WriteLine(numThreads + " threads"); + } + + CountdownEvent startingGun = new CountdownEvent(1); + IList<ThreadClass> threads = new List<ThreadClass>(); + + int iters = AtLeast(1000); + int idCount = TestUtil.NextInt(Random(), 100, 10000); + + double reopenChance = Random().NextDouble() * 0.01; + double deleteChance = Random().NextDouble() * 0.25; + double addChance = Random().NextDouble() * 0.5; + + for (int t = 0; t < numThreads; t++) + { + int threadID = t; + Random threadRandom = new Random(Random().Next()); + ThreadClass thread = new ThreadAnonymousInnerClassHelper(w, mgr, missing, rt, startingGun, iters, idCount, reopenChance, deleteChance, addChance, t, threadID, threadRandom); + threads.Add(thread); + thread.Start(); + } + + startingGun.Signal(); + + foreach (ThreadClass thread in threads) + { + thread.Join(); + } + mgr.MaybeRefresh(); + Assert.AreEqual(0, rt.Count); + + rt.Dispose(); + mgr.Dispose(); + w.Dispose(); + dir.Dispose(); + } + + private class SearcherFactoryAnonymousInnerClassHelper : SearcherFactory + { + public override IndexSearcher NewSearcher(IndexReader r) + { + return new IndexSearcher(r); + } + } + + private class LiveFieldValuesAnonymousInnerClassHelper : LiveFieldValues<IndexSearcher, int?> + { + public LiveFieldValuesAnonymousInnerClassHelper(SearcherManager mgr, int missing) + : base(mgr, missing) + { + } + + protected override int? LookupFromSearcher(IndexSearcher s, string id) + { + TermQuery tq = new TermQuery(new Term("id", id)); + TopDocs hits = s.Search(tq, 1); + Assert.IsTrue(hits.TotalHits <= 1); + if (hits.TotalHits == 0) + { + return null; + } + else + { + Document doc = s.Doc(hits.ScoreDocs[0].Doc); + return (int)doc.GetField("field").GetNumericValue(); + } + } + } + + private class ThreadAnonymousInnerClassHelper : ThreadClass + { + private IndexWriter w; + private SearcherManager Mgr; + private int? Missing; + private LiveFieldValues<IndexSearcher, int?> Rt; + private CountdownEvent StartingGun; + private int Iters; + private int IdCount; + private double ReopenChance; + private double DeleteChance; + private double AddChance; + private int t; + private int ThreadID; + private Random ThreadRandom; + + public ThreadAnonymousInnerClassHelper(IndexWriter w, SearcherManager mgr, int? missing, LiveFieldValues<IndexSearcher, int?> rt, CountdownEvent startingGun, int iters, int idCount, double reopenChance, double deleteChance, double addChance, int t, int threadID, Random threadRandom) + { + this.w = w; + this.Mgr = mgr; + this.Missing = missing; + this.Rt = rt; + this.StartingGun = startingGun; + this.Iters = iters; + this.IdCount = idCount; + this.ReopenChance = reopenChance; + this.DeleteChance = deleteChance; + this.AddChance = addChance; + this.t = t; + this.ThreadID = threadID; + this.ThreadRandom = threadRandom; + } + + public override void Run() + { + try + { + IDictionary<string, int?> values = new Dictionary<string, int?>(); + IList<string> allIDs = new SynchronizedList<string>(); + + StartingGun.Wait(); + for (int iter = 0; iter < Iters; iter++) + { + // Add/update a document + Document doc = new Document(); + // Threads must not update the same id at the + // same time: + if (ThreadRandom.NextDouble() <= AddChance) + { + string id = string.Format(CultureInfo.InvariantCulture, "{0}_{1:X4}", ThreadID, ThreadRandom.Next(IdCount)); + int field = ThreadRandom.Next(int.MaxValue); + doc.Add(new StringField("id", id, Field.Store.YES)); + doc.Add(new Int32Field("field", (int)field, Field.Store.YES)); + w.UpdateDocument(new Term("id", id), doc); + Rt.Add(id, field); + if (!values.ContainsKey(id))//Key didn't exist before + { + allIDs.Add(id); + } + values[id] = field; + } + + if (allIDs.Count > 0 && ThreadRandom.NextDouble() <= DeleteChance) + { + string randomID = allIDs[ThreadRandom.Next(allIDs.Count)]; + w.DeleteDocuments(new Term("id", randomID)); + Rt.Delete(randomID); + values[randomID] = Missing; + } + + if (ThreadRandom.NextDouble() <= ReopenChance || Rt.Count > 10000) + { + //System.out.println("refresh @ " + rt.Size()); + Mgr.MaybeRefresh(); + if (VERBOSE) + { + IndexSearcher s = Mgr.Acquire(); + try + { + Console.WriteLine("TEST: reopen " + s); + } + finally + { + Mgr.Release(s); + } + Console.WriteLine("TEST: " + values.Count + " values"); + } + } + + if (ThreadRandom.Next(10) == 7) + { + Assert.AreEqual(null, Rt.Get("foo")); + } + + if (allIDs.Count > 0) + { + string randomID = allIDs[ThreadRandom.Next(allIDs.Count)]; + int? expected = values[randomID]; + if (expected == Missing) + { + expected = null; + } + Assert.AreEqual(expected, Rt.Get(randomID), "id=" + randomID); + } + } + } + catch (Exception t) + { + throw new Exception(t.Message, t); + } + } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestMatchAllDocsQuery.cs ---------------------------------------------------------------------- diff --git a/src/Lucene.Net.Tests/Search/TestMatchAllDocsQuery.cs b/src/Lucene.Net.Tests/Search/TestMatchAllDocsQuery.cs new file mode 100644 index 0000000..3d8dc7e --- /dev/null +++ b/src/Lucene.Net.Tests/Search/TestMatchAllDocsQuery.cs @@ -0,0 +1,115 @@ +/* + * 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 Lucene.Net.Documents; + +namespace Lucene.Net.Search +{ + using NUnit.Framework; + using Analyzer = Lucene.Net.Analysis.Analyzer; + using Directory = Lucene.Net.Store.Directory; + using DirectoryReader = Lucene.Net.Index.DirectoryReader; + using Document = Documents.Document; + using Field = Field; + using IndexReader = Lucene.Net.Index.IndexReader; + using IndexWriter = Lucene.Net.Index.IndexWriter; + using LuceneTestCase = Lucene.Net.Util.LuceneTestCase; + using MockAnalyzer = Lucene.Net.Analysis.MockAnalyzer; + using Term = Lucene.Net.Index.Term; + + /// <summary> + /// Tests MatchAllDocsQuery. + /// + /// </summary> + [TestFixture] + public class TestMatchAllDocsQuery : LuceneTestCase + { + private Analyzer Analyzer; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + Analyzer = new MockAnalyzer(Random()); + } + + [Test] + public virtual void TestQuery() + { + Directory dir = NewDirectory(); + IndexWriter iw = new IndexWriter(dir, NewIndexWriterConfig(TEST_VERSION_CURRENT, Analyzer).SetMaxBufferedDocs(2).SetMergePolicy(NewLogMergePolicy())); + AddDoc("one", iw, 1f); + AddDoc("two", iw, 20f); + AddDoc("three four", iw, 300f); + IndexReader ir = DirectoryReader.Open(iw, true); + + IndexSearcher @is = NewSearcher(ir); + ScoreDoc[] hits; + + hits = @is.Search(new MatchAllDocsQuery(), null, 1000).ScoreDocs; + Assert.AreEqual(3, hits.Length); + Assert.AreEqual("one", @is.Doc(hits[0].Doc).Get("key")); + Assert.AreEqual("two", @is.Doc(hits[1].Doc).Get("key")); + Assert.AreEqual("three four", @is.Doc(hits[2].Doc).Get("key")); + + // some artificial queries to trigger the use of skipTo(): + + BooleanQuery bq = new BooleanQuery(); + bq.Add(new MatchAllDocsQuery(), Occur.MUST); + bq.Add(new MatchAllDocsQuery(), Occur.MUST); + hits = @is.Search(bq, null, 1000).ScoreDocs; + Assert.AreEqual(3, hits.Length); + + bq = new BooleanQuery(); + bq.Add(new MatchAllDocsQuery(), Occur.MUST); + bq.Add(new TermQuery(new Term("key", "three")), Occur.MUST); + hits = @is.Search(bq, null, 1000).ScoreDocs; + Assert.AreEqual(1, hits.Length); + + iw.DeleteDocuments(new Term("key", "one")); + ir.Dispose(); + ir = DirectoryReader.Open(iw, true); + @is = NewSearcher(ir); + + hits = @is.Search(new MatchAllDocsQuery(), null, 1000).ScoreDocs; + Assert.AreEqual(2, hits.Length); + + iw.Dispose(); + ir.Dispose(); + dir.Dispose(); + } + + [Test] + public virtual void TestEquals() + { + Query q1 = new MatchAllDocsQuery(); + Query q2 = new MatchAllDocsQuery(); + Assert.IsTrue(q1.Equals(q2)); + q1.Boost = 1.5f; + Assert.IsFalse(q1.Equals(q2)); + } + + private void AddDoc(string text, IndexWriter iw, float boost) + { + Document doc = new Document(); + Field f = NewTextField("key", text, Field.Store.YES); + f.Boost = boost; + doc.Add(f); + iw.AddDocument(doc); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestMinShouldMatch2.cs ---------------------------------------------------------------------- diff --git a/src/Lucene.Net.Tests/Search/TestMinShouldMatch2.cs b/src/Lucene.Net.Tests/Search/TestMinShouldMatch2.cs new file mode 100644 index 0000000..6e27f37 --- /dev/null +++ b/src/Lucene.Net.Tests/Search/TestMinShouldMatch2.cs @@ -0,0 +1,431 @@ +using System.Linq; +using Lucene.Net.Documents; +using Lucene.Net.Index; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Lucene.Net.Search +{ + using Lucene.Net.Support; + using NUnit.Framework; + using AtomicReader = Lucene.Net.Index.AtomicReader; + using BooleanWeight = Lucene.Net.Search.BooleanQuery.BooleanWeight; + using BytesRef = Lucene.Net.Util.BytesRef; + using DefaultSimilarity = Lucene.Net.Search.Similarities.DefaultSimilarity; + using Directory = Lucene.Net.Store.Directory; + using DirectoryReader = Lucene.Net.Index.DirectoryReader; + + /* + * 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 LuceneTestCase = Lucene.Net.Util.LuceneTestCase; + using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter; + using SimScorer = Lucene.Net.Search.Similarities.Similarity.SimScorer; + using SimWeight = Lucene.Net.Search.Similarities.Similarity.SimWeight; + using SortedSetDocValues = Lucene.Net.Index.SortedSetDocValues; + using SortedSetDocValuesField = SortedSetDocValuesField; + using StringField = StringField; + using Term = Lucene.Net.Index.Term; + using TermContext = Lucene.Net.Index.TermContext; + using TestUtil = Lucene.Net.Util.TestUtil; + + /// <summary> + /// tests BooleanScorer2's minShouldMatch </summary> + [SuppressCodecs("Appending", "Lucene3x", "Lucene40", "Lucene41")] + [TestFixture] + public class TestMinShouldMatch2 : LuceneTestCase + { + internal static Directory Dir; + internal static DirectoryReader r; + internal static AtomicReader atomicReader; + internal static IndexSearcher Searcher; + + internal static readonly string[] AlwaysTerms = new string[] { "a" }; + internal static readonly string[] CommonTerms = new string[] { "b", "c", "d" }; + internal static readonly string[] MediumTerms = new string[] { "e", "f", "g" }; + internal static readonly string[] RareTerms = new string[] { "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" }; + + /// <summary> + /// LUCENENET specific + /// Is non-static because Similarity and TimeZone are not static. + /// </summary> + [OneTimeSetUp] + public void BeforeClass() + { + Dir = NewDirectory(); + RandomIndexWriter iw = new RandomIndexWriter(Random(), Dir, Similarity, TimeZone); + int numDocs = AtLeast(300); + for (int i = 0; i < numDocs; i++) + { + Document doc = new Document(); + + AddSome(doc, AlwaysTerms); + + if (Random().Next(100) < 90) + { + AddSome(doc, CommonTerms); + } + if (Random().Next(100) < 50) + { + AddSome(doc, MediumTerms); + } + if (Random().Next(100) < 10) + { + AddSome(doc, RareTerms); + } + iw.AddDocument(doc); + } + iw.ForceMerge(1); + iw.Dispose(); + r = DirectoryReader.Open(Dir); + atomicReader = GetOnlySegmentReader(r); + Searcher = new IndexSearcher(atomicReader); + Searcher.Similarity = new DefaultSimilarityAnonymousInnerClassHelper(); + } + + private class DefaultSimilarityAnonymousInnerClassHelper : DefaultSimilarity + { + public DefaultSimilarityAnonymousInnerClassHelper() + { + } + + public override float QueryNorm(float sumOfSquaredWeights) + { + return 1; // we disable queryNorm, both for debugging and ease of impl + } + } + + [OneTimeTearDown] + public static void AfterClass() + { + atomicReader.Dispose(); + Dir.Dispose(); + Searcher = null; + atomicReader = null; + r = null; + Dir = null; + } + + private static void AddSome(Document doc, string[] values) + { + IList<string> list = Arrays.AsList(values); + Collections.Shuffle(list); + int howMany = TestUtil.NextInt(Random(), 1, list.Count); + for (int i = 0; i < howMany; i++) + { + doc.Add(new StringField("field", list[i], Field.Store.NO)); + doc.Add(new SortedSetDocValuesField("dv", new BytesRef(list[i]))); + } + } + + private Scorer Scorer(string[] values, int minShouldMatch, bool slow) + { + BooleanQuery bq = new BooleanQuery(); + foreach (string value in values) + { + bq.Add(new TermQuery(new Term("field", value)), Occur.SHOULD); + } + bq.MinimumNumberShouldMatch = minShouldMatch; + + BooleanWeight weight = (BooleanWeight)Searcher.CreateNormalizedWeight(bq); + + if (slow) + { + return new SlowMinShouldMatchScorer(weight, atomicReader, Searcher); + } + else + { + return weight.GetScorer((AtomicReaderContext)atomicReader.Context, null); + } + } + + private void AssertNext(Scorer expected, Scorer actual) + { + if (actual == null) + { + Assert.AreEqual(DocIdSetIterator.NO_MORE_DOCS, expected.NextDoc()); + return; + } + int doc; + while ((doc = expected.NextDoc()) != DocIdSetIterator.NO_MORE_DOCS) + { + Assert.AreEqual(doc, actual.NextDoc()); + Assert.AreEqual(expected.Freq, actual.Freq); + float expectedScore = expected.GetScore(); + float actualScore = actual.GetScore(); + Assert.AreEqual(expectedScore, actualScore, CheckHits.ExplainToleranceDelta(expectedScore, actualScore)); + } + Assert.AreEqual(DocIdSetIterator.NO_MORE_DOCS, actual.NextDoc()); + } + + private void AssertAdvance(Scorer expected, Scorer actual, int amount) + { + if (actual == null) + { + Assert.AreEqual(DocIdSetIterator.NO_MORE_DOCS, expected.NextDoc()); + return; + } + int prevDoc = 0; + int doc; + while ((doc = expected.Advance(prevDoc + amount)) != DocIdSetIterator.NO_MORE_DOCS) + { + Assert.AreEqual(doc, actual.Advance(prevDoc + amount)); + Assert.AreEqual(expected.Freq, actual.Freq); + float expectedScore = expected.GetScore(); + float actualScore = actual.GetScore(); + Assert.AreEqual(expectedScore, actualScore, CheckHits.ExplainToleranceDelta(expectedScore, actualScore)); + prevDoc = doc; + } + Assert.AreEqual(DocIdSetIterator.NO_MORE_DOCS, actual.Advance(prevDoc + amount)); + } + + /// <summary> + /// simple test for next(): minShouldMatch=2 on 3 terms (one common, one medium, one rare) </summary> + [Test] + public virtual void TestNextCMR2() + { + for (int common = 0; common < CommonTerms.Length; common++) + { + for (int medium = 0; medium < MediumTerms.Length; medium++) + { + for (int rare = 0; rare < RareTerms.Length; rare++) + { + Scorer expected = Scorer(new string[] { CommonTerms[common], MediumTerms[medium], RareTerms[rare] }, 2, true); + Scorer actual = Scorer(new string[] { CommonTerms[common], MediumTerms[medium], RareTerms[rare] }, 2, false); + AssertNext(expected, actual); + } + } + } + } + + /// <summary> + /// simple test for advance(): minShouldMatch=2 on 3 terms (one common, one medium, one rare) </summary> + [Test] + public virtual void TestAdvanceCMR2() + { + for (int amount = 25; amount < 200; amount += 25) + { + for (int common = 0; common < CommonTerms.Length; common++) + { + for (int medium = 0; medium < MediumTerms.Length; medium++) + { + for (int rare = 0; rare < RareTerms.Length; rare++) + { + Scorer expected = Scorer(new string[] { CommonTerms[common], MediumTerms[medium], RareTerms[rare] }, 2, true); + Scorer actual = Scorer(new string[] { CommonTerms[common], MediumTerms[medium], RareTerms[rare] }, 2, false); + AssertAdvance(expected, actual, amount); + } + } + } + } + } + + /// <summary> + /// test next with giant bq of all terms with varying minShouldMatch </summary> + [Test] + public virtual void TestNextAllTerms() + { + IList<string> termsList = new List<string>(); + termsList.AddRange(Arrays.AsList(CommonTerms)); + termsList.AddRange(Arrays.AsList(MediumTerms)); + termsList.AddRange(Arrays.AsList(RareTerms)); + string[] terms = termsList.ToArray(); + + for (int minNrShouldMatch = 1; minNrShouldMatch <= terms.Length; minNrShouldMatch++) + { + Scorer expected = Scorer(terms, minNrShouldMatch, true); + Scorer actual = Scorer(terms, minNrShouldMatch, false); + AssertNext(expected, actual); + } + } + + /// <summary> + /// test advance with giant bq of all terms with varying minShouldMatch </summary> + [Test] + public virtual void TestAdvanceAllTerms() + { + IList<string> termsList = new List<string>(); + termsList.AddRange(Arrays.AsList(CommonTerms)); + termsList.AddRange(Arrays.AsList(MediumTerms)); + termsList.AddRange(Arrays.AsList(RareTerms)); + string[] terms = termsList.ToArray(); + + for (int amount = 25; amount < 200; amount += 25) + { + for (int minNrShouldMatch = 1; minNrShouldMatch <= terms.Length; minNrShouldMatch++) + { + Scorer expected = Scorer(terms, minNrShouldMatch, true); + Scorer actual = Scorer(terms, minNrShouldMatch, false); + AssertAdvance(expected, actual, amount); + } + } + } + + /// <summary> + /// test next with varying numbers of terms with varying minShouldMatch </summary> + [Test] + public virtual void TestNextVaryingNumberOfTerms() + { + IList<string> termsList = new List<string>(); + termsList.AddRange(Arrays.AsList(CommonTerms)); + termsList.AddRange(Arrays.AsList(MediumTerms)); + termsList.AddRange(Arrays.AsList(RareTerms)); + Collections.Shuffle(termsList); + + for (int numTerms = 2; numTerms <= termsList.Count; numTerms++) + { + string[] terms = termsList.SubList(0, numTerms).ToArray(/*new string[0]*/); + for (int minNrShouldMatch = 1; minNrShouldMatch <= terms.Length; minNrShouldMatch++) + { + Scorer expected = Scorer(terms, minNrShouldMatch, true); + Scorer actual = Scorer(terms, minNrShouldMatch, false); + AssertNext(expected, actual); + } + } + } + + /// <summary> + /// test advance with varying numbers of terms with varying minShouldMatch </summary> + [Test] + public virtual void TestAdvanceVaryingNumberOfTerms() + { + IList<string> termsList = new List<string>(); + termsList.AddRange(Arrays.AsList(CommonTerms)); + termsList.AddRange(Arrays.AsList(MediumTerms)); + termsList.AddRange(Arrays.AsList(RareTerms)); + Collections.Shuffle(termsList); + + for (int amount = 25; amount < 200; amount += 25) + { + for (int numTerms = 2; numTerms <= termsList.Count; numTerms++) + { + string[] terms = termsList.SubList(0, numTerms).ToArray(/*new string[0]*/); + for (int minNrShouldMatch = 1; minNrShouldMatch <= terms.Length; minNrShouldMatch++) + { + Scorer expected = Scorer(terms, minNrShouldMatch, true); + Scorer actual = Scorer(terms, minNrShouldMatch, false); + AssertAdvance(expected, actual, amount); + } + } + } + } + + // TODO: more tests + + // a slow min-should match scorer that uses a docvalues field. + // later, we can make debugging easier as it can record the set of ords it currently matched + // and e.g. print out their values and so on for the document + internal class SlowMinShouldMatchScorer : Scorer + { + internal int CurrentDoc = -1; // current docid + internal int CurrentMatched = -1; // current number of terms matched + + internal readonly SortedSetDocValues Dv; + internal readonly int MaxDoc; + + internal readonly HashSet<long?> Ords = new HashSet<long?>(); + internal readonly SimScorer[] Sims; + internal readonly int MinNrShouldMatch; + + internal double Score_Renamed = float.NaN; + + internal SlowMinShouldMatchScorer(BooleanWeight weight, AtomicReader reader, IndexSearcher searcher) + : base(weight) + { + this.Dv = reader.GetSortedSetDocValues("dv"); + this.MaxDoc = reader.MaxDoc; + BooleanQuery bq = (BooleanQuery)weight.Query; + this.MinNrShouldMatch = bq.MinimumNumberShouldMatch; + this.Sims = new SimScorer[(int)Dv.ValueCount]; + foreach (BooleanClause clause in bq.GetClauses()) + { + Debug.Assert(!clause.IsProhibited); + Debug.Assert(!clause.IsRequired); + Term term = ((TermQuery)clause.Query).Term; + long ord = Dv.LookupTerm(term.Bytes); + if (ord >= 0) + { + bool success = Ords.Add(ord); + Debug.Assert(success); // no dups + TermContext context = TermContext.Build(reader.Context, term); + SimWeight w = weight.Similarity.ComputeWeight(1f, searcher.CollectionStatistics("field"), searcher.TermStatistics(term, context)); + var dummy = w.GetValueForNormalization(); // ignored + w.Normalize(1F, 1F); + Sims[(int)ord] = weight.Similarity.GetSimScorer(w, (AtomicReaderContext)reader.Context); + } + } + } + + public override float GetScore() + { + Debug.Assert(Score_Renamed != 0, CurrentMatched.ToString()); + return (float)Score_Renamed * ((BooleanWeight)m_weight).Coord(CurrentMatched, ((BooleanWeight)m_weight).MaxCoord); + } + + public override int Freq + { + get { return CurrentMatched; } + } + + public override int DocID + { + get { return CurrentDoc; } + } + + public override int NextDoc() + { + Debug.Assert(CurrentDoc != NO_MORE_DOCS); + for (CurrentDoc = CurrentDoc + 1; CurrentDoc < MaxDoc; CurrentDoc++) + { + CurrentMatched = 0; + Score_Renamed = 0; + Dv.SetDocument(CurrentDoc); + long ord; + while ((ord = Dv.NextOrd()) != SortedSetDocValues.NO_MORE_ORDS) + { + if (Ords.Contains(ord)) + { + CurrentMatched++; + Score_Renamed += Sims[(int)ord].Score(CurrentDoc, 1); + } + } + if (CurrentMatched >= MinNrShouldMatch) + { + return CurrentDoc; + } + } + return CurrentDoc = NO_MORE_DOCS; + } + + public override int Advance(int target) + { + int doc; + while ((doc = NextDoc()) < target) + { + } + return doc; + } + + public override long GetCost() + { + return MaxDoc; + } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/lucenenet/blob/96822396/src/Lucene.Net.Tests/Search/TestMultiPhraseQuery.cs ---------------------------------------------------------------------- diff --git a/src/Lucene.Net.Tests/Search/TestMultiPhraseQuery.cs b/src/Lucene.Net.Tests/Search/TestMultiPhraseQuery.cs new file mode 100644 index 0000000..0995c13 --- /dev/null +++ b/src/Lucene.Net.Tests/Search/TestMultiPhraseQuery.cs @@ -0,0 +1,631 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Lucene.Net.Documents; + +namespace Lucene.Net.Search +{ + using NUnit.Framework; + using BytesRef = Lucene.Net.Util.BytesRef; + + /* + * 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 CannedTokenStream = Lucene.Net.Analysis.CannedTokenStream; + using DefaultSimilarity = Lucene.Net.Search.Similarities.DefaultSimilarity; + using Directory = Lucene.Net.Store.Directory; + using DirectoryReader = Lucene.Net.Index.DirectoryReader; + using Document = Documents.Document; + using Field = Field; + 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 MultiFields = Lucene.Net.Index.MultiFields; + using RAMDirectory = Lucene.Net.Store.RAMDirectory; + using RandomIndexWriter = Lucene.Net.Index.RandomIndexWriter; + using Term = Lucene.Net.Index.Term; + using TermsEnum = Lucene.Net.Index.TermsEnum; + using TextField = TextField; + using Token = Lucene.Net.Analysis.Token; + + /// <summary> + /// this class tests the MultiPhraseQuery class. + /// + /// + /// </summary> + [TestFixture] + public class TestMultiPhraseQuery : LuceneTestCase + { + [Test] + public virtual void TestPhrasePrefix() + { + Directory indexStore = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), indexStore, Similarity, TimeZone); + Add("blueberry pie", writer); + Add("blueberry strudel", writer); + Add("blueberry pizza", writer); + Add("blueberry chewing gum", writer); + Add("bluebird pizza", writer); + Add("bluebird foobar pizza", writer); + Add("piccadilly circus", writer); + + IndexReader reader = writer.Reader; + IndexSearcher searcher = NewSearcher(reader); + + // search for "blueberry pi*": + MultiPhraseQuery query1 = new MultiPhraseQuery(); + // search for "strawberry pi*": + MultiPhraseQuery query2 = new MultiPhraseQuery(); + query1.Add(new Term("body", "blueberry")); + query2.Add(new Term("body", "strawberry")); + + LinkedList<Term> termsWithPrefix = new LinkedList<Term>(); + + // this TermEnum gives "piccadilly", "pie" and "pizza". + string prefix = "pi"; + TermsEnum te = MultiFields.GetFields(reader).GetTerms("body").GetIterator(null); + te.SeekCeil(new BytesRef(prefix)); + do + { + string s = te.Term.Utf8ToString(); + if (s.StartsWith(prefix)) + { + termsWithPrefix.AddLast(new Term("body", s)); + } + else + { + break; + } + } while (te.Next() != null); + + query1.Add(termsWithPrefix.ToArray(/*new Term[0]*/)); + Assert.AreEqual("body:\"blueberry (piccadilly pie pizza)\"", query1.ToString()); + query2.Add(termsWithPrefix.ToArray(/*new Term[0]*/)); + Assert.AreEqual("body:\"strawberry (piccadilly pie pizza)\"", query2.ToString()); + + ScoreDoc[] result; + result = searcher.Search(query1, null, 1000).ScoreDocs; + Assert.AreEqual(2, result.Length); + result = searcher.Search(query2, null, 1000).ScoreDocs; + Assert.AreEqual(0, result.Length); + + // search for "blue* pizza": + MultiPhraseQuery query3 = new MultiPhraseQuery(); + termsWithPrefix.Clear(); + prefix = "blue"; + te.SeekCeil(new BytesRef(prefix)); + + do + { + if (te.Term.Utf8ToString().StartsWith(prefix)) + { + termsWithPrefix.AddLast(new Term("body", te.Term.Utf8ToString())); + } + } while (te.Next() != null); + + query3.Add(termsWithPrefix.ToArray(/*new Term[0]*/)); + query3.Add(new Term("body", "pizza")); + + result = searcher.Search(query3, null, 1000).ScoreDocs; + Assert.AreEqual(2, result.Length); // blueberry pizza, bluebird pizza + Assert.AreEqual("body:\"(blueberry bluebird) pizza\"", query3.ToString()); + + // test slop: + query3.Slop = 1; + result = searcher.Search(query3, null, 1000).ScoreDocs; + + // just make sure no exc: + searcher.Explain(query3, 0); + + Assert.AreEqual(3, result.Length); // blueberry pizza, bluebird pizza, bluebird + // foobar pizza + + MultiPhraseQuery query4 = new MultiPhraseQuery(); + try + { + query4.Add(new Term("field1", "foo")); + query4.Add(new Term("field2", "foobar")); + Assert.Fail(); + } +#pragma warning disable 168 + catch (System.ArgumentException e) +#pragma warning restore 168 + { + // okay, all terms must belong to the same field + } + + writer.Dispose(); + reader.Dispose(); + indexStore.Dispose(); + } + + // LUCENE-2580 + [Test] + public virtual void TestTall() + { + Directory indexStore = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), indexStore, Similarity, TimeZone); + Add("blueberry chocolate pie", writer); + Add("blueberry chocolate tart", writer); + IndexReader r = writer.Reader; + writer.Dispose(); + + IndexSearcher searcher = NewSearcher(r); + MultiPhraseQuery q = new MultiPhraseQuery(); + q.Add(new Term("body", "blueberry")); + q.Add(new Term("body", "chocolate")); + q.Add(new Term[] { new Term("body", "pie"), new Term("body", "tart") }); + Assert.AreEqual(2, searcher.Search(q, 1).TotalHits); + r.Dispose(); + indexStore.Dispose(); + } + + //ORIGINAL LINE: @Ignore public void testMultiSloppyWithRepeats() throws java.io.IOException + [Test] + [Ignore("This appears to be a known issue")] + public virtual void TestMultiSloppyWithRepeats() //LUCENE-3821 fixes sloppy phrase scoring, except for this known problem + { + Directory indexStore = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), indexStore, Similarity, TimeZone); + Add("a b c d e f g h i k", writer); + IndexReader r = writer.Reader; + writer.Dispose(); + + IndexSearcher searcher = NewSearcher(r); + + MultiPhraseQuery q = new MultiPhraseQuery(); + // this will fail, when the scorer would propagate [a] rather than [a,b], + q.Add(new Term[] { new Term("body", "a"), new Term("body", "b") }); + q.Add(new Term[] { new Term("body", "a") }); + q.Slop = 6; + Assert.AreEqual(1, searcher.Search(q, 1).TotalHits); // should match on "a b" + + r.Dispose(); + indexStore.Dispose(); + } + + [Test] + public virtual void TestMultiExactWithRepeats() + { + Directory indexStore = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), indexStore, Similarity, TimeZone); + Add("a b c d e f g h i k", writer); + IndexReader r = writer.Reader; + writer.Dispose(); + + IndexSearcher searcher = NewSearcher(r); + MultiPhraseQuery q = new MultiPhraseQuery(); + q.Add(new Term[] { new Term("body", "a"), new Term("body", "d") }, 0); + q.Add(new Term[] { new Term("body", "a"), new Term("body", "f") }, 2); + Assert.AreEqual(1, searcher.Search(q, 1).TotalHits); // should match on "a b" + r.Dispose(); + indexStore.Dispose(); + } + + private void Add(string s, RandomIndexWriter writer) + { + Document doc = new Document(); + doc.Add(NewTextField("body", s, Field.Store.YES)); + writer.AddDocument(doc); + } + + [Test] + public virtual void TestBooleanQueryContainingSingleTermPrefixQuery() + { + // this tests against bug 33161 (now fixed) + // In order to cause the bug, the outer query must have more than one term + // and all terms required. + // The contained PhraseMultiQuery must contain exactly one term array. + Directory indexStore = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), indexStore, Similarity, TimeZone); + Add("blueberry pie", writer); + Add("blueberry chewing gum", writer); + Add("blue raspberry pie", writer); + + IndexReader reader = writer.Reader; + IndexSearcher searcher = NewSearcher(reader); + // this query will be equivalent to +body:pie +body:"blue*" + BooleanQuery q = new BooleanQuery(); + q.Add(new TermQuery(new Term("body", "pie")), Occur.MUST); + + MultiPhraseQuery trouble = new MultiPhraseQuery(); + trouble.Add(new Term[] { new Term("body", "blueberry"), new Term("body", "blue") }); + q.Add(trouble, Occur.MUST); + + // exception will be thrown here without fix + ScoreDoc[] hits = searcher.Search(q, null, 1000).ScoreDocs; + + Assert.AreEqual(2, hits.Length, "Wrong number of hits"); + + // just make sure no exc: + searcher.Explain(q, 0); + + writer.Dispose(); + reader.Dispose(); + indexStore.Dispose(); + } + + [Test] + public virtual void TestPhrasePrefixWithBooleanQuery() + { + Directory indexStore = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), indexStore, Similarity, TimeZone); + Add("this is a test", "object", writer); + Add("a note", "note", writer); + + IndexReader reader = writer.Reader; + IndexSearcher searcher = NewSearcher(reader); + + // this query will be equivalent to +type:note +body:"a t*" + BooleanQuery q = new BooleanQuery(); + q.Add(new TermQuery(new Term("type", "note")), Occur.MUST); + + MultiPhraseQuery trouble = new MultiPhraseQuery(); + trouble.Add(new Term("body", "a")); + trouble.Add(new Term[] { new Term("body", "test"), new Term("body", "this") }); + q.Add(trouble, Occur.MUST); + + // exception will be thrown here without fix for #35626: + ScoreDoc[] hits = searcher.Search(q, null, 1000).ScoreDocs; + Assert.AreEqual(0, hits.Length, "Wrong number of hits"); + writer.Dispose(); + reader.Dispose(); + indexStore.Dispose(); + } + + [Test] + public virtual void TestNoDocs() + { + Directory indexStore = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), indexStore, Similarity, TimeZone); + Add("a note", "note", writer); + + IndexReader reader = writer.Reader; + IndexSearcher searcher = NewSearcher(reader); + + MultiPhraseQuery q = new MultiPhraseQuery(); + q.Add(new Term("body", "a")); + q.Add(new Term[] { new Term("body", "nope"), new Term("body", "nope") }); + Assert.AreEqual(0, searcher.Search(q, null, 1).TotalHits, "Wrong number of hits"); + + // just make sure no exc: + searcher.Explain(q, 0); + + writer.Dispose(); + reader.Dispose(); + indexStore.Dispose(); + } + + [Test] + public virtual void TestHashCodeAndEquals() + { + MultiPhraseQuery query1 = new MultiPhraseQuery(); + MultiPhraseQuery query2 = new MultiPhraseQuery(); + + Assert.AreEqual(query1.GetHashCode(), query2.GetHashCode()); + Assert.IsTrue(query1.Equals(query2)); + Assert.AreEqual(query1, query2); + + Term term1 = new Term("someField", "someText"); + + query1.Add(term1); + query2.Add(term1); + + Assert.AreEqual(query1.GetHashCode(), query2.GetHashCode()); + Assert.AreEqual(query1, query2); + + Term term2 = new Term("someField", "someMoreText"); + + query1.Add(term2); + + Assert.IsFalse(query1.GetHashCode() == query2.GetHashCode()); + Assert.IsFalse(query1.Equals(query2)); + + query2.Add(term2); + + Assert.AreEqual(query1.GetHashCode(), query2.GetHashCode()); + Assert.AreEqual(query1, query2); + } + + private void Add(string s, string type, RandomIndexWriter writer) + { + Document doc = new Document(); + doc.Add(NewTextField("body", s, Field.Store.YES)); + doc.Add(NewStringField("type", type, Field.Store.NO)); + writer.AddDocument(doc); + } + + // LUCENE-2526 + [Test] + public virtual void TestEmptyToString() + { + (new MultiPhraseQuery()).ToString(); + } + + [Test] + public virtual void TestCustomIDF() + { + Directory indexStore = NewDirectory(); + RandomIndexWriter writer = new RandomIndexWriter(Random(), indexStore, Similarity, TimeZone); + Add("this is a test", "object", writer); + Add("a note", "note", writer); + + IndexReader reader = writer.Reader; + IndexSearcher searcher = NewSearcher(reader); + searcher.Similarity = new DefaultSimilarityAnonymousInnerClassHelper(this); + + MultiPhraseQuery query = new MultiPhraseQuery(); + query.Add(new Term[] { new Term("body", "this"), new Term("body", "that") }); + query.Add(new Term("body", "is")); + Weight weight = query.CreateWeight(searcher); + Assert.AreEqual(10f * 10f, weight.GetValueForNormalization(), 0.001f); + + writer.Dispose(); + reader.Dispose(); + indexStore.Dispose(); + } + + private class DefaultSimilarityAnonymousInnerClassHelper : DefaultSimilarity + { + private readonly TestMultiPhraseQuery OuterInstance; + + public DefaultSimilarityAnonymousInnerClassHelper(TestMultiPhraseQuery outerInstance) + { + this.OuterInstance = outerInstance; + } + + public override Explanation IdfExplain(CollectionStatistics collectionStats, TermStatistics[] termStats) + { + return new Explanation(10f, "just a test"); + } + } + + [Test] + public virtual void TestZeroPosIncr() + { + Directory dir = new RAMDirectory(); + Token[] tokens = new Token[3]; + tokens[0] = new Token(); + tokens[0].Append("a"); + tokens[0].PositionIncrement = 1; + tokens[1] = new Token(); + tokens[1].Append("b"); + tokens[1].PositionIncrement = 0; + tokens[2] = new Token(); + tokens[2].Append("c"); + tokens[2].PositionIncrement = 0; + + RandomIndexWriter writer = new RandomIndexWriter(Random(), dir, Similarity, TimeZone); + Document doc = new Document(); + doc.Add(new TextField("field", new CannedTokenStream(tokens))); + writer.AddDocument(doc); + doc = new Document(); + doc.Add(new TextField("field", new CannedTokenStream(tokens))); + writer.AddDocument(doc); + IndexReader r = writer.Reader; + writer.Dispose(); + IndexSearcher s = NewSearcher(r); + MultiPhraseQuery mpq = new MultiPhraseQuery(); + //mpq.setSlop(1); + + // NOTE: not great that if we do the else clause here we + // get different scores! MultiPhraseQuery counts that + // phrase as occurring twice per doc (it should be 1, I + // think?). this is because MultipleTermPositions is able to + // return the same position more than once (0, in this + // case): + if (true) + { + mpq.Add(new Term[] { new Term("field", "b"), new Term("field", "c") }, 0); + mpq.Add(new Term[] { new Term("field", "a") }, 0); + } + else + { +#pragma warning disable 162 + mpq.Add(new Term[] { new Term("field", "a") }, 0); + mpq.Add(new Term[] { new Term("field", "b"), new Term("field", "c") }, 0); +#pragma warning restore 162 + } + TopDocs hits = s.Search(mpq, 2); + Assert.AreEqual(2, hits.TotalHits); + Assert.AreEqual(hits.ScoreDocs[0].Score, hits.ScoreDocs[1].Score, 1e-5); + /* + for(int hit=0;hit<hits.TotalHits;hit++) { + ScoreDoc sd = hits.ScoreDocs[hit]; + System.out.println(" hit doc=" + sd.Doc + " score=" + sd.Score); + } + */ + r.Dispose(); + dir.Dispose(); + } + + private static Token MakeToken(string text, int posIncr) + { + Token t = new Token(); + t.Append(text); + t.PositionIncrement = posIncr; + return t; + } + + private static readonly Token[] INCR_0_DOC_TOKENS = new Token[] { MakeToken("x", 1), MakeToken("a", 1), MakeToken("1", 0), MakeToken("m", 1), MakeToken("b", 1), MakeToken("1", 0), MakeToken("n", 1), MakeToken("c", 1), MakeToken("y", 1) }; + + private static readonly Token[] INCR_0_QUERY_TOKENS_AND = new Token[] { MakeToken("a", 1), MakeToken("1", 0), MakeToken("b", 1), MakeToken("1", 0), MakeToken("c", 1) }; + + private static readonly Token[][] INCR_0_QUERY_TOKENS_AND_OR_MATCH = new Token[][] { new Token[] { MakeToken("a", 1) }, new Token[] { MakeToken("x", 1), MakeToken("1", 0) }, new Token[] { MakeToken("b", 2) }, new Token[] { MakeToken("x", 2), MakeToken("1", 0) }, new Token[] { MakeToken("c", 3) } }; + + private static readonly Token[][] INCR_0_QUERY_TOKENS_AND_OR_NO_MATCHN = new Token[][] { new Token[] { MakeToken("x", 1) }, new Token[] { MakeToken("a", 1), MakeToken("1", 0) }, new Token[] { MakeToken("x", 2) }, new Token[] { MakeToken("b", 2), MakeToken("1", 0) }, new Token[] { MakeToken("c", 3) } }; + + /// <summary> + /// using query parser, MPQ will be created, and will not be strict about having all query terms + /// in each position - one of each position is sufficient (OR logic) + /// </summary> + [Test] + public virtual void TestZeroPosIncrSloppyParsedAnd() + { + MultiPhraseQuery q = new MultiPhraseQuery(); + q.Add(new Term[] { new Term("field", "a"), new Term("field", "1") }, -1); + q.Add(new Term[] { new Term("field", "b"), new Term("field", "1") }, 0); + q.Add(new Term[] { new Term("field", "c") }, 1); + DoTestZeroPosIncrSloppy(q, 0); + q.Slop = 1; + DoTestZeroPosIncrSloppy(q, 0); + q.Slop = 2; + DoTestZeroPosIncrSloppy(q, 1); + } + + private void DoTestZeroPosIncrSloppy(Query q, int nExpected) + { + Directory dir = NewDirectory(); // random dir + IndexWriterConfig cfg = NewIndexWriterConfig(TEST_VERSION_CURRENT, null); + IndexWriter writer = new IndexWriter(dir, cfg); + Document doc = new Document(); + doc.Add(new TextField("field", new CannedTokenStream(INCR_0_DOC_TOKENS))); + writer.AddDocument(doc); + IndexReader r = DirectoryReader.Open(writer, false); + writer.Dispose(); + IndexSearcher s = NewSearcher(r); + + if (VERBOSE) + { + Console.WriteLine("QUERY=" + q); + } + + TopDocs hits = s.Search(q, 1); + Assert.AreEqual(nExpected, hits.TotalHits, "wrong number of results"); + + if (VERBOSE) + { + for (int hit = 0; hit < hits.TotalHits; hit++) + { + ScoreDoc sd = hits.ScoreDocs[hit]; + Console.WriteLine(" hit doc=" + sd.Doc + " score=" + sd.Score); + } + } + + r.Dispose(); + dir.Dispose(); + } + + /// <summary> + /// PQ AND Mode - Manually creating a phrase query + /// </summary> + [Test] + public virtual void TestZeroPosIncrSloppyPqAnd() + { + PhraseQuery pq = new PhraseQuery(); + int pos = -1; + foreach (Token tap in INCR_0_QUERY_TOKENS_AND) + { + pos += tap.PositionIncrement; + pq.Add(new Term("field", tap.ToString()), pos); + } + DoTestZeroPosIncrSloppy(pq, 0); + pq.Slop = 1; + DoTestZeroPosIncrSloppy(pq, 0); + pq.Slop = 2; + DoTestZeroPosIncrSloppy(pq, 1); + } + + /// <summary> + /// MPQ AND Mode - Manually creating a multiple phrase query + /// </summary> + [Test] + public virtual void TestZeroPosIncrSloppyMpqAnd() + { + MultiPhraseQuery mpq = new MultiPhraseQuery(); + int pos = -1; + foreach (Token tap in INCR_0_QUERY_TOKENS_AND) + { + pos += tap.PositionIncrement; + mpq.Add(new Term[] { new Term("field", tap.ToString()) }, pos); //AND logic + } + DoTestZeroPosIncrSloppy(mpq, 0); + mpq.Slop = 1; + DoTestZeroPosIncrSloppy(mpq, 0); + mpq.Slop = 2; + DoTestZeroPosIncrSloppy(mpq, 1); + } + + /// <summary> + /// MPQ Combined AND OR Mode - Manually creating a multiple phrase query + /// </summary> + [Test] + public virtual void TestZeroPosIncrSloppyMpqAndOrMatch() + { + MultiPhraseQuery mpq = new MultiPhraseQuery(); + foreach (Token[] tap in INCR_0_QUERY_TOKENS_AND_OR_MATCH) + { + Term[] terms = TapTerms(tap); + int pos = tap[0].PositionIncrement - 1; + mpq.Add(terms, pos); //AND logic in pos, OR across lines + } + DoTestZeroPosIncrSloppy(mpq, 0); + mpq.Slop = 1; + DoTestZeroPosIncrSloppy(mpq, 0); + mpq.Slop = 2; + DoTestZeroPosIncrSloppy(mpq, 1); + } + + /// <summary> + /// MPQ Combined AND OR Mode - Manually creating a multiple phrase query - with no match + /// </summary> + [Test] + public virtual void TestZeroPosIncrSloppyMpqAndOrNoMatch() + { + MultiPhraseQuery mpq = new MultiPhraseQuery(); + foreach (Token[] tap in INCR_0_QUERY_TOKENS_AND_OR_NO_MATCHN) + { + Term[] terms = TapTerms(tap); + int pos = tap[0].PositionIncrement - 1; + mpq.Add(terms, pos); //AND logic in pos, OR across lines + } + DoTestZeroPosIncrSloppy(mpq, 0); + mpq.Slop = 2; + DoTestZeroPosIncrSloppy(mpq, 0); + } + + private Term[] TapTerms(Token[] tap) + { + Term[] terms = new Term[tap.Length]; + for (int i = 0; i < terms.Length; i++) + { + terms[i] = new Term("field", tap[i].ToString()); + } + return terms; + } + + [Test] + public virtual void TestNegativeSlop() + { + MultiPhraseQuery query = new MultiPhraseQuery(); + query.Add(new Term("field", "two")); + query.Add(new Term("field", "one")); + try + { + query.Slop = -2; + Assert.Fail("didn't get expected exception"); + } +#pragma warning disable 168 + catch (System.ArgumentException expected) +#pragma warning restore 168 + { + // expected exception + } + } + } +} \ No newline at end of file
