| // Lucene version compatibility level 4.8.1 |
| using Lucene.Net.Analysis; |
| using Lucene.Net.Documents; |
| using Lucene.Net.Index; |
| using Lucene.Net.Join; |
| using Lucene.Net.Search; |
| using Lucene.Net.Store; |
| using Lucene.Net.Util; |
| using NUnit.Framework; |
| using System; |
| using System.Collections.Generic; |
| using System.Text; |
| |
| namespace Lucene.Net.Tests.Join |
| { |
| /* |
| * 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. |
| */ |
| |
| public class TestBlockJoinValidation : LuceneTestCase |
| { |
| public const int AMOUNT_OF_SEGMENTS = 5; |
| public const int AMOUNT_OF_PARENT_DOCS = 10; |
| public const int AMOUNT_OF_CHILD_DOCS = 5; |
| public const int AMOUNT_OF_DOCS_IN_SEGMENT = AMOUNT_OF_PARENT_DOCS + AMOUNT_OF_PARENT_DOCS * AMOUNT_OF_CHILD_DOCS; |
| |
| private Directory directory; |
| private IndexReader indexReader; |
| private IndexSearcher indexSearcher; |
| private Filter parentsFilter; |
| |
| [SetUp] |
| public override void SetUp() |
| { |
| directory = NewDirectory(); |
| IndexWriterConfig config = new IndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(Random)); |
| IndexWriter indexWriter = new IndexWriter(directory, config); |
| for (int i = 0; i < AMOUNT_OF_SEGMENTS; i++) |
| { |
| IList<Document> segmentDocs = CreateDocsForSegment(i); |
| indexWriter.AddDocuments(segmentDocs); |
| indexWriter.Commit(); |
| } |
| indexReader = DirectoryReader.Open(indexWriter, Random.NextBoolean()); |
| indexWriter.Dispose(); |
| indexSearcher = new IndexSearcher(indexReader); |
| parentsFilter = new FixedBitSetCachingWrapperFilter(new QueryWrapperFilter(new WildcardQuery(new Term("parent", "*")))); |
| } |
| |
| [Test] |
| public void TestNextDocValidationForToParentBjq() |
| { |
| Query parentQueryWithRandomChild = CreateChildrenQueryWithOneParent(GetRandomChildNumber(0)); |
| var blockJoinQuery = new ToParentBlockJoinQuery(parentQueryWithRandomChild, parentsFilter, ScoreMode.None); |
| |
| var ex = Assert.Throws<InvalidOperationException>(() => indexSearcher.Search(blockJoinQuery, 1)); |
| StringAssert.Contains("child query must only match non-parent docs", ex.Message); |
| |
| } |
| |
| [Test] |
| public void TestAdvanceValidationForToParentBjq() |
| { |
| int randomChildNumber = GetRandomChildNumber(0); |
| // we need to make advance method meet wrong document, so random child number |
| // in BJQ must be greater than child number in Boolean clause |
| int nextRandomChildNumber = GetRandomChildNumber(randomChildNumber); |
| Query parentQueryWithRandomChild = CreateChildrenQueryWithOneParent(nextRandomChildNumber); |
| ToParentBlockJoinQuery blockJoinQuery = new ToParentBlockJoinQuery(parentQueryWithRandomChild, parentsFilter, ScoreMode.None); |
| // advance() method is used by ConjunctionScorer, so we need to create Boolean conjunction query |
| BooleanQuery conjunctionQuery = new BooleanQuery(); |
| WildcardQuery childQuery = new WildcardQuery(new Term("child", CreateFieldValue(randomChildNumber))); |
| conjunctionQuery.Add(new BooleanClause(childQuery, Occur.MUST)); |
| conjunctionQuery.Add(new BooleanClause(blockJoinQuery, Occur.MUST)); |
| |
| var ex = Assert.Throws<InvalidOperationException>(() => indexSearcher.Search(conjunctionQuery, 1)); |
| StringAssert.Contains("child query must only match non-parent docs", ex.Message); |
| } |
| |
| [Test] |
| public void TestNextDocValidationForToChildBjq() |
| { |
| Query parentQueryWithRandomChild = CreateParentsQueryWithOneChild(GetRandomChildNumber(0)); |
| var blockJoinQuery = new ToChildBlockJoinQuery(parentQueryWithRandomChild, parentsFilter, false); |
| |
| var ex = Assert.Throws<InvalidOperationException>(() => indexSearcher.Search(blockJoinQuery, 1)); |
| StringAssert.Contains(ToChildBlockJoinQuery.INVALID_QUERY_MESSAGE, ex.Message); |
| } |
| |
| [Test] |
| public void TestAdvanceValidationForToChildBjq() |
| { |
| int randomChildNumber = GetRandomChildNumber(0); |
| // we need to make advance method meet wrong document, so random child number |
| // in BJQ must be greater than child number in Boolean clause |
| int nextRandomChildNumber = GetRandomChildNumber(randomChildNumber); |
| Query parentQueryWithRandomChild = CreateParentsQueryWithOneChild(nextRandomChildNumber); |
| var blockJoinQuery = new ToChildBlockJoinQuery(parentQueryWithRandomChild, parentsFilter, false); |
| // advance() method is used by ConjunctionScorer, so we need to create Boolean conjunction query |
| var conjunctionQuery = new BooleanQuery(); |
| var childQuery = new WildcardQuery(new Term("child", CreateFieldValue(randomChildNumber))); |
| conjunctionQuery.Add(new BooleanClause(childQuery, Occur.MUST)); |
| conjunctionQuery.Add(new BooleanClause(blockJoinQuery, Occur.MUST)); |
| |
| var ex = Assert.Throws<InvalidOperationException>(() => indexSearcher.Search(conjunctionQuery, 1)); |
| StringAssert.Contains(ToChildBlockJoinQuery.INVALID_QUERY_MESSAGE, ex.Message); |
| } |
| |
| [TearDown] |
| public override void TearDown() |
| { |
| indexReader.Dispose(); |
| directory.Dispose(); |
| } |
| |
| private IList<Document> CreateDocsForSegment(int segmentNumber) |
| { |
| IList<IList<Document>> blocks = new List<IList<Document>>(AMOUNT_OF_PARENT_DOCS); |
| for (int i = 0; i < AMOUNT_OF_PARENT_DOCS; i++) |
| { |
| blocks.Add(CreateParentDocWithChildren(segmentNumber, i)); |
| } |
| IList<Document> result = new List<Document>(AMOUNT_OF_DOCS_IN_SEGMENT); |
| foreach (IList<Document> block in blocks) |
| { |
| result.AddRange(block); |
| } |
| return result; |
| } |
| |
| private IList<Document> CreateParentDocWithChildren(int segmentNumber, int parentNumber) |
| { |
| IList<Document> result = new List<Document>(AMOUNT_OF_CHILD_DOCS + 1); |
| for (int i = 0; i < AMOUNT_OF_CHILD_DOCS; i++) |
| { |
| result.Add(CreateChildDoc(segmentNumber, parentNumber, i)); |
| } |
| result.Add(CreateParentDoc(segmentNumber, parentNumber)); |
| return result; |
| } |
| |
| private Document CreateParentDoc(int segmentNumber, int parentNumber) |
| { |
| Document result = new Document(); |
| result.Add(NewStringField("id", CreateFieldValue(segmentNumber * AMOUNT_OF_PARENT_DOCS + parentNumber), Field.Store.YES)); |
| result.Add(NewStringField("parent", CreateFieldValue(parentNumber), Field.Store.NO)); |
| return result; |
| } |
| |
| private Document CreateChildDoc(int segmentNumber, int parentNumber, int childNumber) |
| { |
| Document result = new Document(); |
| result.Add(NewStringField("id", CreateFieldValue(segmentNumber * AMOUNT_OF_PARENT_DOCS + parentNumber, childNumber), Field.Store.YES)); |
| result.Add(NewStringField("child", CreateFieldValue(childNumber), Field.Store.NO)); |
| return result; |
| } |
| |
| private static string CreateFieldValue(params int[] documentNumbers) |
| { |
| StringBuilder stringBuilder = new StringBuilder(); |
| foreach (int documentNumber in documentNumbers) |
| { |
| if (stringBuilder.Length > 0) |
| { |
| stringBuilder.Append("_"); |
| } |
| stringBuilder.Append(documentNumber); |
| } |
| return stringBuilder.ToString(); |
| } |
| |
| private static Query CreateChildrenQueryWithOneParent(int childNumber) |
| { |
| TermQuery childQuery = new TermQuery(new Term("child", CreateFieldValue(childNumber))); |
| Query randomParentQuery = new TermQuery(new Term("id", CreateFieldValue(GetRandomParentId()))); |
| BooleanQuery childrenQueryWithRandomParent = new BooleanQuery(); |
| childrenQueryWithRandomParent.Add(new BooleanClause(childQuery, Occur.SHOULD)); |
| childrenQueryWithRandomParent.Add(new BooleanClause(randomParentQuery, Occur.SHOULD)); |
| return childrenQueryWithRandomParent; |
| } |
| |
| private static Query CreateParentsQueryWithOneChild(int randomChildNumber) |
| { |
| BooleanQuery childQueryWithRandomParent = new BooleanQuery(); |
| Query parentsQuery = new TermQuery(new Term("parent", CreateFieldValue(GetRandomParentNumber()))); |
| childQueryWithRandomParent.Add(new BooleanClause(parentsQuery, Occur.SHOULD)); |
| childQueryWithRandomParent.Add(new BooleanClause(RandomChildQuery(randomChildNumber), Occur.SHOULD)); |
| return childQueryWithRandomParent; |
| } |
| |
| private static int GetRandomParentId() => Random.Next(AMOUNT_OF_PARENT_DOCS*AMOUNT_OF_SEGMENTS); |
| |
| private static int GetRandomParentNumber() => Random.Next(AMOUNT_OF_PARENT_DOCS); |
| |
| private static Query RandomChildQuery(int randomChildNumber) |
| { |
| return new TermQuery(new Term("id", CreateFieldValue(GetRandomParentId(), randomChildNumber))); |
| } |
| |
| private static int GetRandomChildNumber(int notLessThan) |
| { |
| return notLessThan + Random.Next(AMOUNT_OF_CHILD_DOCS - notLessThan); |
| } |
| } |
| } |