Index: lucene/spatial/src/java/org/apache/lucene/spatial/prefix/ContainsPrefixTreeFilter.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/java/org/apache/lucene/spatial/prefix/ContainsPrefixTreeFilter.java (revision 1493637) +++ lucene/spatial/src/java/org/apache/lucene/spatial/prefix/ContainsPrefixTreeFilter.java (revision ) @@ -41,6 +41,15 @@ */ public class ContainsPrefixTreeFilter extends AbstractPrefixTreeFilter { + /** + * If the spatial data for a document is comprised of multiple overlapping or adjacent parts, + * it might fail to match a query shape when doing the CONTAINS predicate when the sum of + * those shapes contain the query shape but none do individually. Set this to false to + * increase performance if you don't care about that circumstance (such as if your indexed + * data doesn't even have such conditions). See LUCENE-5062. + */ + protected boolean supportMultiOverlappingIndexedShapes = true; + public ContainsPrefixTreeFilter(Shape queryShape, String fieldName, SpatialPrefixTree grid, int detailLevel) { super(queryShape, fieldName, grid, detailLevel); } @@ -65,18 +74,25 @@ if (termsEnum == null)//signals all done return null; - //Leaf docs match all query shape + // Leaf docs match all query shape SmallDocSet leafDocs = getLeafDocs(cell, acceptContains); - // Get the AND of all child results + // Get the AND of all child results (into combinedSubResults) SmallDocSet combinedSubResults = null; - Collection subCells = cell.getSubCells(queryShape); + // Optimization: use null subCellsFilter when we know cell is within the query shape. + Shape subCellsFilter = queryShape; + if (cell.getLevel() != 0 && ((cell.getShapeRel() == null || cell.getShapeRel() == SpatialRelation.WITHIN))) { + subCellsFilter = null; + assert cell.getShape().relate(queryShape) == SpatialRelation.WITHIN; + } + Collection subCells = cell.getSubCells(subCellsFilter); for (Cell subCell : subCells) { if (!seekExact(subCell)) combinedSubResults = null; else if (subCell.getLevel() == detailLevel) combinedSubResults = getDocs(subCell, acceptContains); - else if (subCell.getShapeRel() == SpatialRelation.WITHIN) + else if (!supportMultiOverlappingIndexedShapes && + subCell.getShapeRel() == SpatialRelation.WITHIN) combinedSubResults = getLeafDocs(subCell, acceptContains); else combinedSubResults = visit(subCell, acceptContains); //recursion @@ -90,7 +106,7 @@ if (combinedSubResults != null) { if (leafDocs == null) return combinedSubResults; - return leafDocs.union(combinedSubResults); + return leafDocs.union(combinedSubResults);//union is 'or' } return leafDocs; } @@ -109,8 +125,12 @@ return collectDocs(acceptContains); } + private Cell lastLeaf = null;//just for assertion + private SmallDocSet getLeafDocs(Cell leafCell, Bits acceptContains) throws IOException { assert new BytesRef(leafCell.getTokenBytes()).equals(termBytes); + assert ! leafCell.equals(lastLeaf);//don't call for same leaf again + lastLeaf = leafCell; BytesRef nextTerm = termsEnum.next(); if (nextTerm == null) { Index: lucene/spatial/src/test/org/apache/lucene/spatial/prefix/SpatialOpRecursivePrefixTreeTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- lucene/spatial/src/test/org/apache/lucene/spatial/prefix/SpatialOpRecursivePrefixTreeTest.java (revision 1493637) +++ lucene/spatial/src/test/org/apache/lucene/spatial/prefix/SpatialOpRecursivePrefixTreeTest.java (revision ) @@ -18,6 +18,7 @@ */ import com.carrotsearch.randomizedtesting.annotations.Repeat; +import com.carrotsearch.randomizedtesting.annotations.Seed; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.shape.Point; import com.spatial4j.core.shape.Rectangle; @@ -111,6 +112,17 @@ } @Test + public void testContainsPairOverlap() throws IOException { + mySetup(3); + adoc("0", new ShapePair(ctx.makeRectangle(0, 33, -128, 128), ctx.makeRectangle(33, 128, -128, 128), true)); + commit(); + Query query = strategy.makeQuery(new SpatialArgs(SpatialOperation.Contains, + ctx.makeRectangle(0, 128, -16, 128))); + SearchResults searchResults = executeQuery(query, 1); + assertEquals(1, searchResults.numFound); + } + + @Test public void testWithinDisjointParts() throws IOException { mySetup(7); //one shape comprised of two parts, quite separated apart @@ -184,10 +196,10 @@ Shape indexedShape; Shape indexedShapeGS; //(grid-snapped) int R = random().nextInt(12); - if (R == 0) {//1 in 10 + if (R == 0) {//1 in 12 indexedShape = null; //no shape for this doc indexedShapeGS = null; - } else if (R % 4 == 0) {//3 in 12 + } else if (R % 3 == 0) {//4-1 in 12 //comprised of more than one shape Rectangle shape1 = randomRectangle(); Rectangle shape2 = randomRectangle();