Java tutorial
/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch 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. */ package org.elasticsearch.index.search.child; import com.carrotsearch.hppc.FloatArrayList; import com.carrotsearch.hppc.IntIntOpenHashMap; import com.carrotsearch.hppc.ObjectObjectOpenHashMap; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.StringField; import org.apache.lucene.index.*; import org.apache.lucene.queries.TermFilter; import org.apache.lucene.search.*; import org.apache.lucene.store.Directory; import org.apache.lucene.util.FixedBitSet; import org.elasticsearch.common.lucene.search.NotFilter; import org.elasticsearch.common.lucene.search.XFilteredQuery; import org.elasticsearch.index.engine.Engine; import org.elasticsearch.index.fielddata.plain.ParentChildIndexFieldData; import org.elasticsearch.index.mapper.Uid; import org.elasticsearch.index.mapper.internal.ParentFieldMapper; import org.elasticsearch.index.mapper.internal.TypeFieldMapper; import org.elasticsearch.index.mapper.internal.UidFieldMapper; import org.elasticsearch.search.internal.ContextIndexSearcher; import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.test.ElasticsearchLuceneTestCase; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import java.io.IOException; import java.util.Map; import java.util.NavigableMap; import java.util.TreeMap; import static org.elasticsearch.index.search.child.ChildrenConstantScoreQueryTests.assertBitSet; import static org.elasticsearch.index.search.child.ChildrenConstantScoreQueryTests.createSearchContext; import static org.elasticsearch.index.search.child.ChildrenQueryTests.assertTopDocs; public class ParentQueryTests extends ElasticsearchLuceneTestCase { @BeforeClass public static void before() throws IOException { forceDefaultCodec(); SearchContext.setCurrent(createSearchContext("test", "parent", "child")); } @AfterClass public static void after() throws IOException { SearchContext.removeCurrent(); } @Test public void testRandom() throws Exception { Directory directory = newDirectory(); RandomIndexWriter indexWriter = new RandomIndexWriter(random(), directory); int numUniqueParentValues = 1 + random().nextInt(TEST_NIGHTLY ? 6000 : 600); String[] parentValues = new String[numUniqueParentValues]; for (int i = 0; i < numUniqueParentValues; i++) { parentValues[i] = Integer.toString(i); } int childDocId = 0; int numParentDocs = 1 + random().nextInt(TEST_NIGHTLY ? 20000 : 1000); ObjectObjectOpenHashMap<String, NavigableMap<String, Float>> parentValueToChildIds = new ObjectObjectOpenHashMap<String, NavigableMap<String, Float>>(); IntIntOpenHashMap childIdToParentId = new IntIntOpenHashMap(); for (int parentDocId = 0; parentDocId < numParentDocs; parentDocId++) { boolean markParentAsDeleted = rarely(); String parentValue = parentValues[random().nextInt(parentValues.length)]; String parent = Integer.toString(parentDocId); Document document = new Document(); document.add(new StringField(UidFieldMapper.NAME, Uid.createUid("parent", parent), Field.Store.NO)); document.add(new StringField(TypeFieldMapper.NAME, "parent", Field.Store.NO)); document.add(new StringField("field1", parentValue, Field.Store.NO)); if (markParentAsDeleted) { document.add(new StringField("delete", "me", Field.Store.NO)); } indexWriter.addDocument(document); int numChildDocs; if (rarely()) { numChildDocs = random().nextInt(TEST_NIGHTLY ? 100 : 25); } else { numChildDocs = random().nextInt(TEST_NIGHTLY ? 40 : 10); } if (parentDocId == numParentDocs - 1 && childIdToParentId.isEmpty()) { // ensure there is at least one child in the index numChildDocs = Math.max(1, numChildDocs); } for (int i = 0; i < numChildDocs; i++) { String child = Integer.toString(childDocId++); boolean markChildAsDeleted = rarely(); boolean filterMe = rarely(); document = new Document(); document.add(new StringField(UidFieldMapper.NAME, Uid.createUid("child", child), Field.Store.YES)); document.add(new StringField(TypeFieldMapper.NAME, "child", Field.Store.NO)); document.add( new StringField(ParentFieldMapper.NAME, Uid.createUid("parent", parent), Field.Store.NO)); if (markChildAsDeleted) { document.add(new StringField("delete", "me", Field.Store.NO)); } if (filterMe) { document.add(new StringField("filter", "me", Field.Store.NO)); } indexWriter.addDocument(document); if (!markParentAsDeleted) { NavigableMap<String, Float> childIdToScore; if (parentValueToChildIds.containsKey(parentValue)) { childIdToScore = parentValueToChildIds.lget(); } else { parentValueToChildIds.put(parentValue, childIdToScore = new TreeMap<String, Float>()); } if (!markChildAsDeleted && !filterMe) { assertFalse("child [" + child + "] already has a score", childIdToScore.containsKey(child)); childIdToScore.put(child, 1f); childIdToParentId.put(Integer.valueOf(child), parentDocId); } } } } // Delete docs that are marked to be deleted. indexWriter.deleteDocuments(new Term("delete", "me")); indexWriter.commit(); IndexReader indexReader = DirectoryReader.open(directory); IndexSearcher searcher = new IndexSearcher(indexReader); Engine.Searcher engineSearcher = new Engine.SimpleSearcher(ParentQueryTests.class.getSimpleName(), searcher); ((TestSearchContext) SearchContext.current()) .setSearcher(new ContextIndexSearcher(SearchContext.current(), engineSearcher)); ParentFieldMapper parentFieldMapper = SearchContext.current().mapperService().documentMapper("child") .parentFieldMapper(); ParentChildIndexFieldData parentChildIndexFieldData = SearchContext.current().fieldData() .getForField(parentFieldMapper); TermFilter rawChildrenFilter = new TermFilter(new Term(TypeFieldMapper.NAME, "child")); Filter rawFilterMe = new NotFilter(new TermFilter(new Term("filter", "me"))); int max = numUniqueParentValues / 4; for (int i = 0; i < max; i++) { // Randomly pick a cached version: there is specific logic inside ChildrenQuery that deals with the fact // that deletes are applied at the top level when filters are cached. Filter childrenFilter; if (random().nextBoolean()) { childrenFilter = SearchContext.current().filterCache().cache(rawChildrenFilter); } else { childrenFilter = rawChildrenFilter; } // Using this in FQ, will invoke / test the Scorer#advance(..) and also let the Weight#scorer not get live docs as acceptedDocs Filter filterMe; if (random().nextBoolean()) { filterMe = SearchContext.current().filterCache().cache(rawFilterMe); } else { filterMe = rawFilterMe; } // Simulate a child update if (random().nextBoolean()) { int numberOfUpdates = 1 + random().nextInt(TEST_NIGHTLY ? 25 : 5); int[] childIds = childIdToParentId.keys().toArray(); for (int j = 0; j < numberOfUpdates; j++) { int childId = childIds[random().nextInt(childIds.length)]; String childUid = Uid.createUid("child", Integer.toString(childId)); indexWriter.deleteDocuments(new Term(UidFieldMapper.NAME, childUid)); Document document = new Document(); document.add(new StringField(UidFieldMapper.NAME, childUid, Field.Store.YES)); document.add(new StringField(TypeFieldMapper.NAME, "child", Field.Store.NO)); String parentUid = Uid.createUid("parent", Integer.toString(childIdToParentId.get(childId))); document.add(new StringField(ParentFieldMapper.NAME, parentUid, Field.Store.NO)); indexWriter.addDocument(document); } indexReader.close(); indexReader = DirectoryReader.open(indexWriter.w, true); searcher = new IndexSearcher(indexReader); engineSearcher = new Engine.SimpleSearcher(ParentConstantScoreQueryTests.class.getSimpleName(), searcher); ((TestSearchContext) SearchContext.current()) .setSearcher(new ContextIndexSearcher(SearchContext.current(), engineSearcher)); } String parentValue = parentValues[random().nextInt(numUniqueParentValues)]; Query parentQuery = new ConstantScoreQuery(new TermQuery(new Term("field1", parentValue))); Query query = new ParentQuery(parentChildIndexFieldData, parentQuery, "parent", childrenFilter); query = new XFilteredQuery(query, filterMe); BitSetCollector collector = new BitSetCollector(indexReader.maxDoc()); int numHits = 1 + random().nextInt(25); TopScoreDocCollector actualTopDocsCollector = TopScoreDocCollector.create(numHits, false); searcher.search(query, MultiCollector.wrap(collector, actualTopDocsCollector)); FixedBitSet actualResult = collector.getResult(); FixedBitSet expectedResult = new FixedBitSet(indexReader.maxDoc()); MockScorer mockScorer = new MockScorer(ScoreType.MAX); // just save one score per parent... mockScorer.scores = new FloatArrayList(); TopScoreDocCollector expectedTopDocsCollector = TopScoreDocCollector.create(numHits, false); expectedTopDocsCollector.setScorer(mockScorer); if (parentValueToChildIds.containsKey(parentValue)) { AtomicReader slowAtomicReader = SlowCompositeReaderWrapper.wrap(indexReader); Terms terms = slowAtomicReader.terms(UidFieldMapper.NAME); if (terms != null) { NavigableMap<String, Float> childIdsAndScore = parentValueToChildIds.lget(); TermsEnum termsEnum = terms.iterator(null); DocsEnum docsEnum = null; for (Map.Entry<String, Float> entry : childIdsAndScore.entrySet()) { TermsEnum.SeekStatus seekStatus = termsEnum .seekCeil(Uid.createUidAsBytes("child", entry.getKey())); if (seekStatus == TermsEnum.SeekStatus.FOUND) { docsEnum = termsEnum.docs(slowAtomicReader.getLiveDocs(), docsEnum, DocsEnum.FLAG_NONE); expectedResult.set(docsEnum.nextDoc()); mockScorer.scores.add(entry.getValue()); expectedTopDocsCollector.collect(docsEnum.docID()); mockScorer.scores.clear(); } else if (seekStatus == TermsEnum.SeekStatus.END) { break; } } } } assertBitSet(actualResult, expectedResult, searcher); assertTopDocs(actualTopDocsCollector.topDocs(), expectedTopDocsCollector.topDocs()); } indexWriter.close(); indexReader.close(); directory.close(); } }