diff --git a/geomesa-accumulo/geomesa-accumulo-datastore/src/test/scala/org/locationtech/geomesa/accumulo/index/QueryFilterSplitterTest.scala b/geomesa-accumulo/geomesa-accumulo-datastore/src/test/scala/org/locationtech/geomesa/accumulo/index/QueryFilterSplitterTest.scala index 3ccdbc3f299a..ac3f87cb5d9b 100644 --- a/geomesa-accumulo/geomesa-accumulo-datastore/src/test/scala/org/locationtech/geomesa/accumulo/index/QueryFilterSplitterTest.scala +++ b/geomesa-accumulo/geomesa-accumulo-datastore/src/test/scala/org/locationtech/geomesa/accumulo/index/QueryFilterSplitterTest.scala @@ -543,10 +543,17 @@ class QueryFilterSplitterTest extends Specification { val filter = f(s"$bboxOr AND ($attribute1 OR $attribute2)") val options = splitter.getQueryOptions(filter) options must haveLength(1) + options must haveLength(1) options.head.strategies must haveLength(1) options.head.strategies.head.index.name mustEqual Z2Index.name compareOr(options.head.strategies.head.primary, bbox1, bbox2) options.head.strategies.head.secondary must beSome // note: filter is too complex to compare explicitly... } + + "handle new case" >> { + val options = splitter.getQueryOptions(and(or(indexedAttr, highCardinaltiyAttr), f(dtg))) + options.foreach(println) + ok + } } } diff --git a/geomesa-index-api/src/main/scala/org/locationtech/geomesa/index/planning/FilterSplitter.scala b/geomesa-index-api/src/main/scala/org/locationtech/geomesa/index/planning/FilterSplitter.scala index 3f4000ce6807..271be05707c3 100644 --- a/geomesa-index-api/src/main/scala/org/locationtech/geomesa/index/planning/FilterSplitter.scala +++ b/geomesa-index-api/src/main/scala/org/locationtech/geomesa/index/planning/FilterSplitter.scala @@ -64,7 +64,17 @@ class FilterSplitter(sft: SimpleFeatureType, indices: Seq[GeoMesaFeatureIndex[_, case a: And => // look for ORs across attributes, e.g. bbox OR dtg val (complex, simple) = a.getChildren.partition(f => f.isInstanceOf[Or] && attributeAndIdCount(f, sft) > 1) - if (complex.isEmpty) { + + // Find the first complex filter (Which is an OR) where each of its children filter is covered by an index + val firstIndexedComplexFilter = complex.find( f => { + val or: Or = f.asInstanceOf[Or] + val children = or.getChildren + children.forall(child => indices.exists(_.getFilterStrategy(child, transform, stats).isDefined)) + }) + // Strategy: Calculate a FilterPlan based on the first complex filter available. + val complexIndexedPlan: Seq[FilterPlan] = Seq() ///firstIndexedComplexFilter.map{} + + if (complex.isEmpty) { // In this case, there is not `complexIndexedPlan`, so nothing needs to be added. // no cross-attribute ORs getSimpleQueryOptions(a, transform).map(fs => FilterPlan(Seq(fs))) } else if (simple.nonEmpty) { @@ -73,7 +83,12 @@ class FilterSplitter(sft: SimpleFeatureType, indices: Seq[GeoMesaFeatureIndex[_, def addComplexPredicates(qf: FilterStrategy): FilterStrategy = qf.copy(andOption(qf.secondary.toSeq ++ complex)) getSimpleQueryOptions(andFilters(simple), transform).map(addComplexPredicates).map(fs => FilterPlan(Seq(fs))) - } else { + // ++complexIndexedPlan + // Return the simple and complex option. The cost calculations will determine which is used. + // Complex ORs with index attributes ought to be evaluated as cheaper than Z3 indices? + } else if (complexIndexedPlan.nonEmpty) { // If there's a complexIndexedPlan prefer it over the fallback? + complexIndexedPlan + } else { // Fallback case would not have a complexIndexPlan logger.warn(s"Falling back to expand/reduce query splitting for filter ${filterToString(filter)}") val dnf = rewriteFilterInDNF(filter).asInstanceOf[Or] expandReduceOrOptions(dnf, transform).map(makeDisjoint)