/*
***************************************************************************************
* Copyright (C) 2006 EsperTech, Inc. All rights reserved. *
* http://www.espertech.com/esper *
* http://www.espertech.com *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the GPL license *
* a copy of which has been included with this distribution in the license.txt file. *
***************************************************************************************
*/
package com.espertech.esper.epl.agg.service;
import com.espertech.esper.client.EventBean;
import com.espertech.esper.collection.MultiKeyUntyped;
import com.espertech.esper.collection.Pair;
import com.espertech.esper.epl.agg.access.AggregationState;
import com.espertech.esper.epl.agg.aggregator.AggregationMethod;
import com.espertech.esper.epl.agg.util.AggregationLocalGroupByColumn;
import com.espertech.esper.epl.agg.util.AggregationLocalGroupByLevel;
import com.espertech.esper.epl.agg.util.AggregationLocalGroupByPlan;
import com.espertech.esper.epl.expression.core.ExprEvaluator;
import com.espertech.esper.epl.expression.core.ExprEvaluatorContext;
import com.espertech.esper.metrics.instrumentation.InstrumentationHelper;
import java.util.*;
/**
* Implementation for handling aggregation with grouping by group-keys.
*/
public abstract class AggSvcGroupLocalGroupByBase implements AggregationService {
protected final boolean isJoin;
protected final AggregationLocalGroupByPlan localGroupByPlan;
// state
protected AggregationMethod[] aggregatorsTopLevel;
protected AggregationState[] statesTopLevel;
protected Map<Object, AggregationMethodPairRow>[] aggregatorsPerLevelAndGroup;
protected List<Pair<Integer, Object>> removedKeys;
protected abstract Object computeGroupKey(AggregationLocalGroupByLevel level, Object groupKey, ExprEvaluator[] partitionEval, EventBean[] eventsPerStream, boolean newData, ExprEvaluatorContext exprEvaluatorContext);
public AggSvcGroupLocalGroupByBase(boolean isJoin,
AggregationLocalGroupByPlan localGroupByPlan) {
this.isJoin = isJoin;
this.localGroupByPlan = localGroupByPlan;
this.aggregatorsPerLevelAndGroup = new Map[localGroupByPlan.getAllLevels().length];
for (int i = 0; i < localGroupByPlan.getAllLevels().length; i++) {
this.aggregatorsPerLevelAndGroup[i] = new HashMap<Object, AggregationMethodPairRow>();
}
removedKeys = new ArrayList<Pair<Integer, Object>>();
}
public void clearResults(ExprEvaluatorContext exprEvaluatorContext) {
clearResults(aggregatorsPerLevelAndGroup, aggregatorsTopLevel, statesTopLevel);
}
public void applyEnter(EventBean[] eventsPerStream, Object groupByKeyProvided, ExprEvaluatorContext exprEvaluatorContext) {
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().qAggregationGroupedApplyEnterLeave(true, localGroupByPlan.getNumMethods(), localGroupByPlan.getNumAccess(), groupByKeyProvided);
}
handleRemovedKeys();
if (localGroupByPlan.getOptionalLevelTop() != null) {
if (aggregatorsTopLevel == null) {
aggregatorsTopLevel = AggSvcGroupByUtil.newAggregators(localGroupByPlan.getOptionalLevelTop().getMethodFactories());
statesTopLevel = AggSvcGroupByUtil.newAccesses(exprEvaluatorContext.getAgentInstanceId(), isJoin, localGroupByPlan.getOptionalLevelTop().getStateFactories(), null, null);
}
aggregateIntoEnter(localGroupByPlan.getOptionalLevelTop(), aggregatorsTopLevel, statesTopLevel, eventsPerStream, exprEvaluatorContext);
internalHandleUpdatedTop();
}
for (int levelNum = 0; levelNum < localGroupByPlan.getAllLevels().length; levelNum++) {
AggregationLocalGroupByLevel level = localGroupByPlan.getAllLevels()[levelNum];
ExprEvaluator[] partitionEval = level.getPartitionEvaluators();
Object groupByKey = computeGroupKey(level, groupByKeyProvided, partitionEval, eventsPerStream, true, exprEvaluatorContext);
AggregationMethodPairRow row = aggregatorsPerLevelAndGroup[levelNum].get(groupByKey);
if (row == null) {
AggregationMethod[] rowAggregators = AggSvcGroupByUtil.newAggregators(level.getMethodFactories());
AggregationState[] rowStates = AggSvcGroupByUtil.newAccesses(exprEvaluatorContext.getAgentInstanceId(), isJoin, level.getStateFactories(), groupByKey, null);
row = new AggregationMethodPairRow(1, rowAggregators, rowStates);
aggregatorsPerLevelAndGroup[levelNum].put(groupByKey, row);
} else {
row.increaseRefcount();
}
aggregateIntoEnter(level, row.getMethods(), row.getStates(), eventsPerStream, exprEvaluatorContext);
internalHandleUpdatedGroup(levelNum, groupByKey, row);
}
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().aAggregationGroupedApplyEnterLeave(true);
}
}
public void applyLeave(EventBean[] eventsPerStream, Object groupByKeyProvided, ExprEvaluatorContext exprEvaluatorContext) {
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().qAggregationGroupedApplyEnterLeave(false, localGroupByPlan.getNumMethods(), localGroupByPlan.getNumAccess(), groupByKeyProvided);
}
if (localGroupByPlan.getOptionalLevelTop() != null) {
if (aggregatorsTopLevel == null) {
aggregatorsTopLevel = AggSvcGroupByUtil.newAggregators(localGroupByPlan.getOptionalLevelTop().getMethodFactories());
statesTopLevel = AggSvcGroupByUtil.newAccesses(exprEvaluatorContext.getAgentInstanceId(), isJoin, localGroupByPlan.getOptionalLevelTop().getStateFactories(), null, null);
}
aggregateIntoLeave(localGroupByPlan.getOptionalLevelTop(), aggregatorsTopLevel, statesTopLevel, eventsPerStream, exprEvaluatorContext);
internalHandleUpdatedTop();
}
for (int levelNum = 0; levelNum < localGroupByPlan.getAllLevels().length; levelNum++) {
AggregationLocalGroupByLevel level = localGroupByPlan.getAllLevels()[levelNum];
ExprEvaluator[] partitionEval = level.getPartitionEvaluators();
Object groupByKey = computeGroupKey(level, groupByKeyProvided, partitionEval, eventsPerStream, true, exprEvaluatorContext);
AggregationMethodPairRow row = aggregatorsPerLevelAndGroup[levelNum].get(groupByKey);
if (row == null) {
AggregationMethod[] rowAggregators = AggSvcGroupByUtil.newAggregators(level.getMethodFactories());
AggregationState[] rowStates = AggSvcGroupByUtil.newAccesses(exprEvaluatorContext.getAgentInstanceId(), isJoin, level.getStateFactories(), groupByKey, null);
row = new AggregationMethodPairRow(1, rowAggregators, rowStates);
aggregatorsPerLevelAndGroup[levelNum].put(groupByKey, row);
} else {
row.decreaseRefcount();
if (row.getRefcount() <= 0) {
removedKeys.add(new Pair<Integer, Object>(levelNum, groupByKey));
}
}
aggregateIntoLeave(level, row.getMethods(), row.getStates(), eventsPerStream, exprEvaluatorContext);
internalHandleUpdatedGroup(levelNum, groupByKey, row);
}
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().aAggregationGroupedApplyEnterLeave(false);
}
}
public Collection<EventBean> getCollectionOfEvents(int column, EventBean[] eventsPerStream, boolean isNewData, ExprEvaluatorContext context) {
AggregationLocalGroupByColumn col = localGroupByPlan.getColumns()[column];
if (col.getPartitionEvaluators().length == 0) {
return col.getPair().getAccessor().getEnumerableEvents(statesTopLevel[col.getPair().getSlot()], eventsPerStream, isNewData, context);
}
Object groupByKey = computeGroupKey(col.getPartitionEvaluators(), eventsPerStream, isNewData, context);
AggregationMethodPairRow row = aggregatorsPerLevelAndGroup[col.getLevelNum()].get(groupByKey);
return col.getPair().getAccessor().getEnumerableEvents(row.getStates()[col.getPair().getSlot()], eventsPerStream, isNewData, context);
}
public Collection<Object> getCollectionScalar(int column, EventBean[] eventsPerStream, boolean isNewData, ExprEvaluatorContext context) {
AggregationLocalGroupByColumn col = localGroupByPlan.getColumns()[column];
if (col.getPartitionEvaluators().length == 0) {
return col.getPair().getAccessor().getEnumerableScalar(statesTopLevel[col.getPair().getSlot()], eventsPerStream, isNewData, context);
}
Object groupByKey = computeGroupKey(col.getPartitionEvaluators(), eventsPerStream, isNewData, context);
AggregationMethodPairRow row = aggregatorsPerLevelAndGroup[col.getLevelNum()].get(groupByKey);
return col.getPair().getAccessor().getEnumerableScalar(row.getStates()[col.getPair().getSlot()], eventsPerStream, isNewData, context);
}
public EventBean getEventBean(int column, EventBean[] eventsPerStream, boolean isNewData, ExprEvaluatorContext context) {
AggregationLocalGroupByColumn col = localGroupByPlan.getColumns()[column];
if (col.getPartitionEvaluators().length == 0) {
return col.getPair().getAccessor().getEnumerableEvent(statesTopLevel[col.getPair().getSlot()], eventsPerStream, isNewData, context);
}
Object groupByKey = computeGroupKey(col.getPartitionEvaluators(), eventsPerStream, isNewData, context);
AggregationMethodPairRow row = aggregatorsPerLevelAndGroup[col.getLevelNum()].get(groupByKey);
return col.getPair().getAccessor().getEnumerableEvent(row.getStates()[col.getPair().getSlot()], eventsPerStream, isNewData, context);
}
public boolean isGrouped() {
return true;
}
public void setRemovedCallback(AggregationRowRemovedCallback callback) {
// not applicable
}
public void accept(AggregationServiceVisitor visitor) {
visitor.visitAggregations(getNumGroups(), aggregatorsTopLevel, statesTopLevel, aggregatorsPerLevelAndGroup);
}
public void acceptGroupDetail(AggregationServiceVisitorWGroupDetail visitor) {
visitor.visitGrouped(getNumGroups());
if (aggregatorsTopLevel != null) {
visitor.visitGroup(null, aggregatorsTopLevel, statesTopLevel);
}
for (int i = 0; i < localGroupByPlan.getAllLevels().length; i++) {
for (Map.Entry<Object, AggregationMethodPairRow> entry : aggregatorsPerLevelAndGroup[i].entrySet()) {
visitor.visitGroup(entry.getKey(), entry.getValue());
}
}
}
public void internalHandleUpdatedGroup(int level, Object groupByKey, AggregationMethodPairRow row) {
// no action required
}
public void internalHandleUpdatedTop() {
// no action required
}
public void internalHandleGroupRemove(Pair<Integer, Object> groupByKey) {
// no action required
}
public void handleRemovedKeys() {
// we collect removed keys lazily on the next enter to reduce the chance of empty-group queries creating empty aggregators temporarily
if (!removedKeys.isEmpty()) {
for (Pair<Integer, Object> removedKey : removedKeys) {
aggregatorsPerLevelAndGroup[removedKey.getFirst()].remove(removedKey.getSecond());
internalHandleGroupRemove(removedKey);
}
removedKeys.clear();
}
}
public Object getGroupKey(int agentInstanceId) {
return null;
}
public Collection<Object> getGroupKeys(ExprEvaluatorContext exprEvaluatorContext) {
throw new UnsupportedOperationException();
}
public static Object computeGroupKey(ExprEvaluator[] partitionEval, EventBean[] eventsPerStream, boolean b, ExprEvaluatorContext exprEvaluatorContext) {
if (partitionEval.length == 1) {
return partitionEval[0].evaluate(eventsPerStream, true, exprEvaluatorContext);
}
Object[] keys = new Object[partitionEval.length];
for (int i = 0; i < keys.length; i++) {
keys[i] = partitionEval[i].evaluate(eventsPerStream, true, exprEvaluatorContext);
}
return new MultiKeyUntyped(keys);
}
public static void aggregateIntoEnter(AggregationLocalGroupByLevel level, AggregationMethod[] methods, AggregationState[] states, EventBean[] eventsPerStream, ExprEvaluatorContext exprEvaluatorContext) {
for (int i = 0; i < level.getMethodEvaluators().length; i++) {
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().qAggNoAccessEnterLeave(true, i, methods[i], level.getMethodFactories()[i].getAggregationExpression());
}
Object value = level.getMethodEvaluators()[i].evaluate(eventsPerStream, true, exprEvaluatorContext);
methods[i].enter(value);
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().aAggNoAccessEnterLeave(true, i, methods[i]);
}
}
for (int i = 0; i < states.length; i++) {
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().qAggAccessEnterLeave(true, i, states[i], level.getStateFactories()[i].getAggregationExpression());
}
states[i].applyEnter(eventsPerStream, exprEvaluatorContext);
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().aAggAccessEnterLeave(true, i, states[i]);
}
}
}
public static void aggregateIntoLeave(AggregationLocalGroupByLevel level, AggregationMethod[] methods, AggregationState[] states, EventBean[] eventsPerStream, ExprEvaluatorContext exprEvaluatorContext) {
for (int i = 0; i < level.getMethodEvaluators().length; i++) {
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().qAggNoAccessEnterLeave(false, i, methods[i], level.getMethodFactories()[i].getAggregationExpression());
}
Object value = level.getMethodEvaluators()[i].evaluate(eventsPerStream, false, exprEvaluatorContext);
methods[i].leave(value);
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().aAggNoAccessEnterLeave(false, i, methods[i]);
}
}
for (int i = 0; i < states.length; i++) {
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().qAggAccessEnterLeave(false, i, states[i], level.getStateFactories()[i].getAggregationExpression());
}
states[i].applyLeave(eventsPerStream, exprEvaluatorContext);
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().aAggAccessEnterLeave(false, i, states[i]);
}
}
}
protected static void clearResults(Map<Object, AggregationMethodPairRow>[] aggregatorsPerLevelAndGroup, AggregationMethod[] aggregatorsTopLevel, AggregationState[] statesTopLevel) {
for (Map<Object, AggregationMethodPairRow> aggregatorsPerGroup : aggregatorsPerLevelAndGroup) {
aggregatorsPerGroup.clear();
}
if (aggregatorsTopLevel != null) {
for (AggregationMethod method : aggregatorsTopLevel) {
method.clear();
}
for (AggregationState state : statesTopLevel) {
state.clear();
}
}
}
public AggregationMethod[] getAggregatorsTopLevel() {
return aggregatorsTopLevel;
}
public void setAggregatorsTopLevel(AggregationMethod[] aggregatorsTopLevel) {
this.aggregatorsTopLevel = aggregatorsTopLevel;
}
public AggregationState[] getStatesTopLevel() {
return statesTopLevel;
}
public void setStatesTopLevel(AggregationState[] statesTopLevel) {
this.statesTopLevel = statesTopLevel;
}
public Map<Object, AggregationMethodPairRow>[] getAggregatorsPerLevelAndGroup() {
return aggregatorsPerLevelAndGroup;
}
public void setAggregatorsPerLevelAndGroup(Map<Object, AggregationMethodPairRow>[] aggregatorsPerLevelAndGroup) {
this.aggregatorsPerLevelAndGroup = aggregatorsPerLevelAndGroup;
}
public List<Pair<Integer, Object>> getRemovedKeys() {
return removedKeys;
}
public void setRemovedKeys(List<Pair<Integer, Object>> removedKeys) {
this.removedKeys = removedKeys;
}
public void stop() {
}
public AggregationService getContextPartitionAggregationService(int agentInstanceId) {
return this;
}
private int getNumGroups() {
int size = aggregatorsTopLevel != null ? 1 : 0;
for (int i = 0; i < localGroupByPlan.getAllLevels().length; i++) {
size += aggregatorsPerLevelAndGroup[i].size();
}
return size;
}
}