@@ -19,6 +19,7 @@ import filodb.core.query.Filter.{Equals, EqualsRegex}
1919import filodb .grpc .GrpcCommonUtils
2020import filodb .query ._
2121import filodb .query .LogicalPlan ._
22+ import filodb .query .RangeFunctionId .AbsentOverTime
2223import filodb .query .exec ._
2324
2425// scalastyle:off file.size.limit
@@ -517,6 +518,22 @@ class MultiPartitionPlanner(val partitionLocationProvider: PartitionLocationProv
517518 materializeForAggregateAssignment(aggregate, assignment, queryContext, timeRangeOverride)
518519 case binaryJoin : BinaryJoin =>
519520 materializeForBinaryJoinAssignment(binaryJoin, assignment, queryContext, timeRangeOverride)
521+ case psw : PeriodicSeriesWithWindowing if psw.function == AbsentOverTime =>
522+ val plans = proportionMap.map(entry => {
523+ val partitionDetails = entry._2
524+ materializeForPartition(logicalPlan, partitionDetails.partitionName,
525+ partitionDetails.grpcEndPoint, partitionDetails.httpEndPoint, queryContext, timeRangeOverride)
526+ }).toSeq
527+ val dispatcher = PlannerUtil .pickDispatcher(plans)
528+ // 0 present 1 absent => 01/10/00 are present. 11 is absent.
529+ val reducer = MultiPartitionReduceAggregateExec (queryContext, dispatcher,
530+ plans.sortWith((x, _) => ! x.isInstanceOf [PromQlRemoteExec ]), AggregationOperator .Absent , Nil )
531+ if (! queryContext.plannerParams.skipAggregatePresent) {
532+ val promQlQueryParams = queryContext.origQueryParams.asInstanceOf [PromQlQueryParams ]
533+ reducer.addRangeVectorTransformer(AggregatePresenter (AggregationOperator .Absent , Nil ,
534+ RangeParams (promQlQueryParams.startSecs, promQlQueryParams.stepSecs, promQlQueryParams.endSecs)))
535+ }
536+ reducer
520537 case _ =>
521538 val plans = proportionMap.map(entry => {
522539 val partitionDetails = entry._2
@@ -698,11 +715,13 @@ class MultiPartitionPlanner(val partitionLocationProvider: PartitionLocationProv
698715 * @param logicalPlan the logic plan.
699716 * @return true if the binary join or aggregation has clauses.
700717 */
701- private def hasJoinClause (logicalPlan : LogicalPlan ): Boolean = {
718+ private def hasJoinOrAggClause (logicalPlan : LogicalPlan ): Boolean = {
702719 logicalPlan match {
703720 case binaryJoin : BinaryJoin => binaryJoin.on.nonEmpty || binaryJoin.ignoring.nonEmpty
704- case aggregate : Aggregate => hasJoinClause(aggregate.vectors)
705- case nonLeaf : NonLeafLogicalPlan => nonLeaf.children.exists(hasJoinClause)
721+ case aggregate : Aggregate => hasJoinOrAggClause(aggregate.vectors)
722+ // AbsentOverTime is a special case that is converted to aggregation.
723+ case psw : PeriodicSeriesWithWindowing if psw.function == AbsentOverTime => true
724+ case nonLeaf : NonLeafLogicalPlan => nonLeaf.children.exists(hasJoinOrAggClause)
706725 case _ => false
707726 }
708727 }
@@ -714,7 +733,7 @@ class MultiPartitionPlanner(val partitionLocationProvider: PartitionLocationProv
714733 val tschemaLabels = getTschemaLabelsIfCanPushdown(aggregate.vectors, queryContext)
715734 // TODO have a more accurate pushdown after location rule is define.
716735 // Right now do not push down any multi-partition namespace plans when on clause exists.
717- val canPushdown = ! (hasMultiPartitionNamespace && hasJoinClause (aggregate)) &&
736+ val canPushdown = ! (hasMultiPartitionNamespace && hasJoinOrAggClause (aggregate)) &&
718737 canPushdownAggregate(aggregate, tschemaLabels, queryContext)
719738 val plan = if (! canPushdown) {
720739 val childPlanRes = walkLogicalPlanTree(aggregate.vectors, queryContext.copy(
0 commit comments