All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.siddhi.core.aggregation.AggregationRuntime Maven / Gradle / Ivy

/*
 * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. 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 io.siddhi.core.aggregation;

import io.siddhi.core.aggregation.persistedaggregation.PersistedIncrementalExecutor;
import io.siddhi.core.config.SiddhiQueryContext;
import io.siddhi.core.event.ComplexEventChunk;
import io.siddhi.core.event.state.MetaStateEvent;
import io.siddhi.core.event.state.StateEvent;
import io.siddhi.core.event.stream.MetaStreamEvent;
import io.siddhi.core.event.stream.StreamEvent;
import io.siddhi.core.exception.QueryableRecordTableException;
import io.siddhi.core.exception.SiddhiAppCreationException;
import io.siddhi.core.executor.ConstantExpressionExecutor;
import io.siddhi.core.executor.ExpressionExecutor;
import io.siddhi.core.executor.VariableExpressionExecutor;
import io.siddhi.core.query.input.stream.single.SingleStreamRuntime;
import io.siddhi.core.query.processor.ProcessingMode;
import io.siddhi.core.query.processor.stream.window.QueryableProcessor;
import io.siddhi.core.query.selector.GroupByKeyGenerator;
import io.siddhi.core.table.Table;
import io.siddhi.core.util.collection.operator.CompiledCondition;
import io.siddhi.core.util.collection.operator.CompiledSelection;
import io.siddhi.core.util.collection.operator.IncrementalAggregateCompileCondition;
import io.siddhi.core.util.collection.operator.MatchingMetaInfoHolder;
import io.siddhi.core.util.parser.ExpressionParser;
import io.siddhi.core.util.parser.OperatorParser;
import io.siddhi.core.util.parser.helper.QueryParserHelper;
import io.siddhi.core.util.snapshot.SnapshotService;
import io.siddhi.core.util.statistics.LatencyTracker;
import io.siddhi.core.util.statistics.MemoryCalculable;
import io.siddhi.core.util.statistics.ThroughputTracker;
import io.siddhi.core.util.statistics.metrics.Level;
import io.siddhi.query.api.aggregation.TimePeriod;
import io.siddhi.query.api.aggregation.Within;
import io.siddhi.query.api.definition.AbstractDefinition;
import io.siddhi.query.api.definition.AggregationDefinition;
import io.siddhi.query.api.definition.Attribute;
import io.siddhi.query.api.definition.StreamDefinition;
import io.siddhi.query.api.exception.SiddhiAppValidationException;
import io.siddhi.query.api.execution.query.selection.OutputAttribute;
import io.siddhi.query.api.execution.query.selection.Selector;
import io.siddhi.query.api.expression.AttributeFunction;
import io.siddhi.query.api.expression.Expression;
import io.siddhi.query.api.expression.Variable;
import io.siddhi.query.api.expression.condition.Compare;
import io.siddhi.query.api.expression.constant.BoolConstant;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static io.siddhi.core.util.SiddhiConstants.AGG_EXTERNAL_TIMESTAMP_COL;
import static io.siddhi.core.util.SiddhiConstants.AGG_LAST_TIMESTAMP_COL;
import static io.siddhi.core.util.SiddhiConstants.AGG_SHARD_ID_COL;
import static io.siddhi.core.util.SiddhiConstants.AGG_START_TIMESTAMP_COL;
import static io.siddhi.core.util.SiddhiConstants.UNKNOWN_STATE;
import static io.siddhi.query.api.expression.Expression.Time.normalizeDuration;

/**
 * Aggregation runtime managing aggregation operations for aggregation definition.
 */
public class AggregationRuntime implements MemoryCalculable {
    private static final Logger LOG = LogManager.getLogger(AggregationRuntime.class);

    private AggregationDefinition aggregationDefinition;
    private boolean isProcessingOnExternalTime;
    private boolean isDistributed;
    private List incrementalDurations;
    private List activeIncrementalDurations;
    private Map incrementalExecutorMap;
    private Map aggregationTables;
    private List tableAttributesNameList;
    private MetaStreamEvent aggregateMetaSteamEvent;
    private List outputExpressionExecutors;
    private Map> aggregateProcessingExecutorsMap;
    private ExpressionExecutor shouldUpdateTimestamp;
    private Map groupByKeyGeneratorMap;
    private boolean isOptimisedLookup;
    private List defaultSelectorList;
    private List groupByVariablesList;
    private boolean isLatestEventColAdded;
    private int baseAggregatorBeginIndex;
    private List finalBaseExpressionsList;

    private IncrementalDataPurger incrementalDataPurger;
    private IncrementalExecutorsInitialiser incrementalExecutorsInitialiser;

    private SingleStreamRuntime singleStreamRuntime;

    private LatencyTracker latencyTrackerFind;
    private ThroughputTracker throughputTrackerFind;

    private boolean isFirstEventArrived;
    private String timeZone;
    private Map> aggregationDurationExecutorMap;

    public AggregationRuntime(AggregationDefinition aggregationDefinition, boolean isProcessingOnExternalTime,
                              boolean isDistributed, List aggregationDurations,
                              Map incrementalExecutorMap,
                              Map aggregationTables,
                              List outputExpressionExecutors,
                              Map> aggregateProcessingExecutorsMap,
                              ExpressionExecutor shouldUpdateTimestamp,
                              Map groupByKeyGeneratorMap,
                              boolean isOptimisedLookup, List defaultSelectorList,
                              List groupByVariablesList,
                              boolean isLatestEventColAdded, int baseAggregatorBeginIndex,
                              List finalBaseExpressionList, IncrementalDataPurger incrementalDataPurger,
                              IncrementalExecutorsInitialiser incrementalExecutorInitialiser,
                              SingleStreamRuntime singleStreamRuntime, MetaStreamEvent tableMetaStreamEvent,
                              LatencyTracker latencyTrackerFind, ThroughputTracker throughputTrackerFind,
                              String timeZone) {
        this.timeZone = timeZone;
        this.aggregationDefinition = aggregationDefinition;
        this.isProcessingOnExternalTime = isProcessingOnExternalTime;
        this.isDistributed = isDistributed;
        this.incrementalDurations = aggregationDurations;
        this.activeIncrementalDurations = aggregationDurations;
        this.incrementalExecutorMap = incrementalExecutorMap;
        this.aggregationTables = aggregationTables;
        this.tableAttributesNameList = tableMetaStreamEvent.getInputDefinitions().get(0).getAttributeList()
                .stream().map(Attribute::getName).collect(Collectors.toList());
        this.outputExpressionExecutors = outputExpressionExecutors;
        this.aggregateProcessingExecutorsMap = aggregateProcessingExecutorsMap;
        this.shouldUpdateTimestamp = shouldUpdateTimestamp;
        this.groupByKeyGeneratorMap = groupByKeyGeneratorMap;
        this.isOptimisedLookup = isOptimisedLookup;
        this.defaultSelectorList = defaultSelectorList;
        this.groupByVariablesList = groupByVariablesList;
        this.isLatestEventColAdded = isLatestEventColAdded;
        this.baseAggregatorBeginIndex = baseAggregatorBeginIndex;
        this.finalBaseExpressionsList = finalBaseExpressionList;

        this.incrementalDataPurger = incrementalDataPurger;
        this.incrementalExecutorsInitialiser = incrementalExecutorInitialiser;

        this.singleStreamRuntime = singleStreamRuntime;
        this.aggregateMetaSteamEvent = new MetaStreamEvent();
        aggregationDefinition.getAttributeList().forEach(this.aggregateMetaSteamEvent::addOutputData);

        this.latencyTrackerFind = latencyTrackerFind;
        this.throughputTrackerFind = throughputTrackerFind;
        this.aggregationDurationExecutorMap = new HashMap<>();
        this.aggregationDurationExecutorMap.put(aggregationDefinition.getId(), incrementalExecutorMap);

    }

    private static void initMetaStreamEvent(MetaStreamEvent metaStreamEvent, AbstractDefinition inputDefinition,
                                            String inputReferenceId) {
        metaStreamEvent.addInputDefinition(inputDefinition);
        metaStreamEvent.setInputReferenceId(inputReferenceId);
        metaStreamEvent.initializeOnAfterWindowData();
        inputDefinition.getAttributeList().forEach(metaStreamEvent::addData);
    }

    private static MetaStreamEvent alterMetaStreamEvent(boolean isOnDemandQuery,
                                                        MetaStreamEvent originalMetaStreamEvent,
                                                        List additionalAttributes) {

        StreamDefinition alteredStreamDef = new StreamDefinition();
        String inputReferenceId = originalMetaStreamEvent.getInputReferenceId();

        if (!isOnDemandQuery) {
            for (Attribute attribute : originalMetaStreamEvent.getLastInputDefinition().getAttributeList()) {
                alteredStreamDef.attribute(attribute.getName(), attribute.getType());
            }
            if (inputReferenceId == null) {
                alteredStreamDef.setId(originalMetaStreamEvent.getLastInputDefinition().getId());
            }
        } else {
            // If it is on-demand query, no original join stream
            alteredStreamDef.setId("OnDemandQueryStream");
        }

        additionalAttributes.forEach(attribute -> alteredStreamDef.attribute(attribute.getName(), attribute.getType()));

        initMetaStreamEvent(originalMetaStreamEvent, alteredStreamDef, inputReferenceId);
        return originalMetaStreamEvent;
    }

    private static MetaStreamEvent createMetaStoreEvent(AbstractDefinition tableDefinition, String referenceId) {
        MetaStreamEvent metaStreamEventForTable = new MetaStreamEvent();
        metaStreamEventForTable.setEventType(MetaStreamEvent.EventType.TABLE);
        initMetaStreamEvent(metaStreamEventForTable, tableDefinition, referenceId);
        return metaStreamEventForTable;
    }

    private static MatchingMetaInfoHolder alterMetaInfoHolderForOnDemandQuery(
            MetaStreamEvent newMetaStreamEventWithStartEnd, MatchingMetaInfoHolder matchingMetaInfoHolder) {

        MetaStateEvent metaStateEvent = new MetaStateEvent(2);
        MetaStreamEvent incomingMetaStreamEvent = matchingMetaInfoHolder.getMetaStateEvent().getMetaStreamEvent(0);
        metaStateEvent.addEvent(newMetaStreamEventWithStartEnd);
        metaStateEvent.addEvent(incomingMetaStreamEvent);

        return new MatchingMetaInfoHolder(metaStateEvent, 0, 1,
                newMetaStreamEventWithStartEnd.getLastInputDefinition(),
                incomingMetaStreamEvent.getLastInputDefinition(), UNKNOWN_STATE);
    }

    private static MatchingMetaInfoHolder createNewStreamTableMetaInfoHolder(MetaStreamEvent metaStreamEvent,
                                                                             MetaStreamEvent metaStoreEvent) {

        MetaStateEvent metaStateEvent = new MetaStateEvent(2);
        metaStateEvent.addEvent(metaStreamEvent);
        metaStateEvent.addEvent(metaStoreEvent);
        return new MatchingMetaInfoHolder(metaStateEvent, 0, 1,
                metaStreamEvent.getLastInputDefinition(), metaStoreEvent.getLastInputDefinition(), UNKNOWN_STATE);
    }

    private static List constructSelectorList(boolean isProcessingOnExternalTime,
                                                               boolean isDistributed,
                                                               boolean isLatestEventColAdded,
                                                               int baseAggregatorBeginIndex,
                                                               int numGroupByVariables,
                                                               List finalBaseExpressions,
                                                               AbstractDefinition incomingOutputStreamDefinition,
                                                               List newGroupByList) {

        List selectorList = new ArrayList<>();
        List attributeList = incomingOutputStreamDefinition.getAttributeList();

        List queryGroupByNames = newGroupByList.stream()
                .map(Variable::getAttributeName).collect(Collectors.toList());
        Variable maxVariable;
        if (!isProcessingOnExternalTime) {
            maxVariable = new Variable(AGG_START_TIMESTAMP_COL);
        } else if (isLatestEventColAdded) {
            maxVariable = new Variable(AGG_LAST_TIMESTAMP_COL);
        } else {
            maxVariable = new Variable(AGG_EXTERNAL_TIMESTAMP_COL);
        }

        int i = 0;
        //Add timestamp selector
        OutputAttribute timestampAttribute;
        if (!isProcessingOnExternalTime && queryGroupByNames.contains(AGG_START_TIMESTAMP_COL)) {
            timestampAttribute = new OutputAttribute(new Variable(AGG_START_TIMESTAMP_COL));
        } else {
            timestampAttribute = new OutputAttribute(attributeList.get(i).getName(),
                    Expression.function("max", new Variable(AGG_START_TIMESTAMP_COL)));
        }
        selectorList.add(timestampAttribute);
        i++;

        if (isDistributed) {
            selectorList.add(new OutputAttribute(AGG_SHARD_ID_COL, Expression.function("max",
                    new Variable(AGG_SHARD_ID_COL))));
            i++;
        }

        if (isProcessingOnExternalTime) {
            OutputAttribute externalTimestampAttribute;
            if (queryGroupByNames.contains(AGG_START_TIMESTAMP_COL)) {
                externalTimestampAttribute = new OutputAttribute(new Variable(AGG_EXTERNAL_TIMESTAMP_COL));
            } else {
                externalTimestampAttribute = new OutputAttribute(attributeList.get(i).getName(),
                        Expression.function("max", new Variable(AGG_EXTERNAL_TIMESTAMP_COL)));
            }
            selectorList.add(externalTimestampAttribute);
            i++;
        }

        for (int j = 0; j < numGroupByVariables; j++) {
            OutputAttribute groupByAttribute;
            Variable variable = new Variable(attributeList.get(i).getName());
            if (queryGroupByNames.contains(variable.getAttributeName())) {
                groupByAttribute = new OutputAttribute(variable);
            } else {
                groupByAttribute = new OutputAttribute(variable.getAttributeName(),
                        Expression.function("incrementalAggregator", "last",
                                new Variable(attributeList.get(i).getName()), maxVariable));
            }
            selectorList.add(groupByAttribute);
            i++;
        }

        if (isLatestEventColAdded) {
            baseAggregatorBeginIndex = baseAggregatorBeginIndex - 1;
        }

        for (; i < baseAggregatorBeginIndex; i++) {
            OutputAttribute outputAttribute;
            Variable variable = new Variable(attributeList.get(i).getName());
            if (queryGroupByNames.contains(variable.getAttributeName())) {
                outputAttribute = new OutputAttribute(variable);
            } else {
                outputAttribute = new OutputAttribute(attributeList.get(i).getName(),
                        Expression.function("incrementalAggregator", "last",
                                new Variable(attributeList.get(i).getName()), maxVariable));
            }
            selectorList.add(outputAttribute);
        }

        if (isLatestEventColAdded) {
            OutputAttribute lastTimestampAttribute = new OutputAttribute(AGG_LAST_TIMESTAMP_COL,
                    Expression.function("max", new Variable(AGG_LAST_TIMESTAMP_COL)));
            selectorList.add(lastTimestampAttribute);
            i++;
        }

        for (Expression finalBaseExpression : finalBaseExpressions) {
            OutputAttribute outputAttribute = new OutputAttribute(attributeList.get(i).getName(), finalBaseExpression);
            selectorList.add(outputAttribute);
            i++;
        }

        return selectorList;
    }

    public AggregationDefinition getAggregationDefinition() {
        return aggregationDefinition;
    }

    public SingleStreamRuntime getSingleStreamRuntime() {
        return singleStreamRuntime;
    }

    public StreamEvent find(StateEvent matchingEvent, CompiledCondition compiledCondition,
                            SiddhiQueryContext siddhiQueryContext) {
        try {
            SnapshotService.getSkipStateStorageThreadLocal().set(true);
            if (throughputTrackerFind != null &&
                    Level.BASIC.compareTo(siddhiQueryContext.getSiddhiAppContext().getRootMetricsLevel()) <= 0) {
                throughputTrackerFind.eventIn();
            }
            if (latencyTrackerFind != null &&
                    Level.BASIC.compareTo(siddhiQueryContext.getSiddhiAppContext().getRootMetricsLevel()) <= 0) {
                latencyTrackerFind.markIn();
            }
            if (!isDistributed && !isFirstEventArrived) {
                // No need to initialise executors if it is distributed
                initialiseExecutors(false);
            }
            return ((IncrementalAggregateCompileCondition) compiledCondition).find(matchingEvent,
                    incrementalExecutorMap, aggregateProcessingExecutorsMap, groupByKeyGeneratorMap,
                    shouldUpdateTimestamp, timeZone);

        } finally {
            SnapshotService.getSkipStateStorageThreadLocal().set(null);
            if (latencyTrackerFind != null &&
                    Level.BASIC.compareTo(siddhiQueryContext.getSiddhiAppContext().getRootMetricsLevel()) <= 0) {
                latencyTrackerFind.markOut();
            }
        }
    }

    public CompiledCondition compileExpression(Expression expression, Within within, Expression per,
                                               List queryGroupByList,
                                               MatchingMetaInfoHolder matchingMetaInfoHolder,
                                               List variableExpressionExecutors,
                                               Map tableMap, SiddhiQueryContext siddhiQueryContext) {

        String aggregationName = aggregationDefinition.getId();
        boolean isOptimisedTableLookup = isOptimisedLookup;
        Map withinTableCompiledConditions = new HashMap<>();
        CompiledCondition withinInMemoryCompileCondition;
        CompiledCondition onCompiledCondition;
        List additionalAttributes = new ArrayList<>();

        // Define additional attribute list
        additionalAttributes.add(new Attribute("_START", Attribute.Type.LONG));
        additionalAttributes.add(new Attribute("_END", Attribute.Type.LONG));

        int lowerGranularitySize = this.activeIncrementalDurations.size() - 1;
        List lowerGranularityAttributes = new ArrayList<>();
        if (isDistributed) {
            //Add additional attributes to get base aggregation timestamps based on current timestamps
            // for values calculated in in-memory in the shards
            for (int i = 0; i < lowerGranularitySize; i++) {
                String attributeName = "_AGG_TIMESTAMP_FILTER_" + i;
                additionalAttributes.add(new Attribute(attributeName, Attribute.Type.LONG));
                lowerGranularityAttributes.add(attributeName);
            }
        }

        // Get table definition. Table definitions for all the tables used to persist aggregates are similar.
        // Therefore it's enough to get the definition from one table.
        AbstractDefinition tableDefinition = aggregationTables.get(activeIncrementalDurations.get(0)).
                getTableDefinition();

        boolean isOnDemandQuery = matchingMetaInfoHolder.getMetaStateEvent().getMetaStreamEvents().length == 1;

        // Alter existing meta stream event or create new one if a meta stream doesn't exist
        // After calling this method the original MatchingMetaInfoHolder's meta stream event would be altered
        // Alter meta info holder to contain stream event and aggregate both when it's a on-demand query
        MetaStreamEvent metaStreamEventForTableLookups;
        if (isOnDemandQuery) {
            metaStreamEventForTableLookups = alterMetaStreamEvent(true, new MetaStreamEvent(), additionalAttributes);
            matchingMetaInfoHolder = alterMetaInfoHolderForOnDemandQuery(metaStreamEventForTableLookups,
                    matchingMetaInfoHolder);
        } else {
            metaStreamEventForTableLookups = alterMetaStreamEvent(false,
                    matchingMetaInfoHolder.getMetaStateEvent().getMetaStreamEvent(0), additionalAttributes);
        }


        // Create new MatchingMetaInfoHolder containing newMetaStreamEventWithStartEnd and table meta event
        String aggReferenceId = matchingMetaInfoHolder.getMetaStateEvent().getMetaStreamEvent(1).getInputReferenceId();
        String referenceName = aggReferenceId == null ? aggregationName : aggReferenceId;

        MetaStreamEvent metaStoreEventForTableLookups = createMetaStoreEvent(tableDefinition, referenceName);

        // Create new MatchingMetaInfoHolder containing metaStreamEventForTableLookups and table meta event
        MatchingMetaInfoHolder metaInfoHolderForTableLookups = createNewStreamTableMetaInfoHolder(
                metaStreamEventForTableLookups, metaStoreEventForTableLookups);

        // Create per expression executor
        ExpressionExecutor perExpressionExecutor;
        if (per != null) {
            perExpressionExecutor = ExpressionParser.parseExpression(per, matchingMetaInfoHolder.getMetaStateEvent(),
                    matchingMetaInfoHolder.getCurrentState(), tableMap, variableExpressionExecutors,
                    false, 0, ProcessingMode.BATCH, false, siddhiQueryContext);
            if (perExpressionExecutor.getReturnType() != Attribute.Type.STRING) {
                throw new SiddhiAppCreationException(
                        "Query " + siddhiQueryContext.getName() + "'s per value expected a string but found "
                                + perExpressionExecutor.getReturnType(),
                        per.getQueryContextStartIndex(), per.getQueryContextEndIndex());
            }
            // Additional Per time function verification at compile time if it is a constant
            if (perExpressionExecutor instanceof ConstantExpressionExecutor) {
                String perValue = ((ConstantExpressionExecutor) perExpressionExecutor).getValue().toString();
                try {
                    normalizeDuration(perValue);
                } catch (SiddhiAppValidationException e) {
                    throw new SiddhiAppValidationException(
                            "Aggregation Query's per value is expected to be of a valid time function of the " +
                                    "following " + TimePeriod.Duration.SECONDS + ", " + TimePeriod.Duration.MINUTES
                                    + ", " + TimePeriod.Duration.HOURS + ", " + TimePeriod.Duration.DAYS + ", "
                                    + TimePeriod.Duration.MONTHS + ", " + TimePeriod.Duration.YEARS + ".");
                }
            }
        } else {
            throw new SiddhiAppCreationException("Syntax Error: Aggregation join query must contain a `per` " +
                    "definition for granularity");
        }

        // Create start and end time expression
        Expression startEndTimeExpression;
        ExpressionExecutor startTimeEndTimeExpressionExecutor;
        if (within != null) {
            if (within.getTimeRange().size() == 1) {
                startEndTimeExpression = new AttributeFunction("incrementalAggregator",
                        "startTimeEndTime", within.getTimeRange().get(0));
            } else { // within.getTimeRange().size() == 2
                startEndTimeExpression = new AttributeFunction("incrementalAggregator",
                        "startTimeEndTime", within.getTimeRange().get(0), within.getTimeRange().get(1));
            }
            startTimeEndTimeExpressionExecutor = ExpressionParser.parseExpression(startEndTimeExpression,
                    matchingMetaInfoHolder.getMetaStateEvent(), matchingMetaInfoHolder.getCurrentState(), tableMap,
                    variableExpressionExecutors, false, 0,
                    ProcessingMode.BATCH, false, siddhiQueryContext);
        } else {
            throw new SiddhiAppCreationException("Syntax Error : Aggregation read query must contain a `within` " +
                    "definition for filtering of aggregation data.");
        }

        // Create within expression
        Expression timeFilterExpression;
        if (isProcessingOnExternalTime) {
            timeFilterExpression = Expression.variable(AGG_EXTERNAL_TIMESTAMP_COL);
        } else {
            timeFilterExpression = Expression.variable(AGG_START_TIMESTAMP_COL);
        }
        Expression withinExpression;
        Expression start = Expression.variable(additionalAttributes.get(0).getName());
        Expression end = Expression.variable(additionalAttributes.get(1).getName());
        Expression compareWithStartTime = Compare.compare(start, Compare.Operator.LESS_THAN_EQUAL,
                timeFilterExpression);
        Expression compareWithEndTime = Compare.compare(timeFilterExpression, Compare.Operator.LESS_THAN, end);
        withinExpression = Expression.and(compareWithStartTime, compareWithEndTime);

        List timestampFilterExecutors = new ArrayList<>();
        if (isDistributed) {
            for (int i = 0; i < lowerGranularitySize; i++) {
                Expression[] expressionArray = new Expression[]{
                        new AttributeFunction("", "currentTimeMillis", null),
                        Expression.value(this.activeIncrementalDurations.get(i + 1).toString())};
                Expression filterExpression = new AttributeFunction("incrementalAggregator",
                        "getAggregationStartTime", expressionArray);
                timestampFilterExecutors.add(ExpressionParser.parseExpression(filterExpression,
                        matchingMetaInfoHolder.getMetaStateEvent(), matchingMetaInfoHolder.getCurrentState(), tableMap,
                        variableExpressionExecutors, false, 0,
                        ProcessingMode.BATCH, false, siddhiQueryContext));
            }
        }

        // Create compile condition per each table used to persist aggregates.
        // These compile conditions are used to check whether the aggregates in tables are within the given duration.
        // Combine with and on condition for table query
        boolean shouldApplyReducedCondition = false;
        Expression reducedExpression = null;

        //Check if there is no on conditions
        if (!(expression instanceof BoolConstant)) {
            // For abstract queryable table
            AggregationExpressionBuilder aggregationExpressionBuilder = new AggregationExpressionBuilder(expression);
            AggregationExpressionVisitor expressionVisitor = new AggregationExpressionVisitor(
                    metaStreamEventForTableLookups.getInputReferenceId(),
                    metaStreamEventForTableLookups.getLastInputDefinition().getAttributeList(),
                    this.tableAttributesNameList
            );
            aggregationExpressionBuilder.build(expressionVisitor);
            shouldApplyReducedCondition = expressionVisitor.applyReducedExpression();
            reducedExpression = expressionVisitor.getReducedExpression();
        }

        Expression withinExpressionTable;
        if (shouldApplyReducedCondition) {
            withinExpressionTable = Expression.and(withinExpression, reducedExpression);
        } else {
            withinExpressionTable = withinExpression;
        }

        List queryGroupByListCopy = new ArrayList<>(queryGroupByList);

        Variable timestampVariable = new Variable(AGG_START_TIMESTAMP_COL);
        List queryGroupByNamesList = queryGroupByListCopy.stream()
                .map(Variable::getAttributeName)
                .collect(Collectors.toList());
        boolean queryGroupByContainsTimestamp = queryGroupByNamesList.remove(AGG_START_TIMESTAMP_COL);

        boolean isQueryGroupBySameAsAggGroupBy =
                queryGroupByListCopy.isEmpty() ||
                        (queryGroupByListCopy.contains(timestampVariable) &&
                                queryGroupByNamesList.equals(groupByVariablesList));

        List variableExpExecutorsForTableLookups = new ArrayList<>();

        Map withinTableCompiledSelection = new HashMap<>();
        if (isOptimisedTableLookup) {
            Selector selector = Selector.selector();

            List groupByList = new ArrayList<>();
            if (!isQueryGroupBySameAsAggGroupBy) {
                if (queryGroupByContainsTimestamp) {
                    if (isProcessingOnExternalTime) {
                        groupByList.add(new Variable(AGG_EXTERNAL_TIMESTAMP_COL));
                    } else {
                        groupByList.add(new Variable(AGG_START_TIMESTAMP_COL));
                    }
                    //Remove timestamp to process the rest
                    queryGroupByListCopy.remove(timestampVariable);
                }
                for (Variable queryGroupBy : queryGroupByListCopy) {
                    String referenceId = queryGroupBy.getStreamId();
                    if (referenceId == null) {
                        if (tableAttributesNameList.contains(queryGroupBy.getAttributeName())) {
                            groupByList.add(queryGroupBy);
                        }
                    } else if (referenceId.equalsIgnoreCase(referenceName)) {
                        groupByList.add(queryGroupBy);
                    }
                }
                // If query group bys are based on joining stream
                if (groupByList.isEmpty()) {
                    isQueryGroupBySameAsAggGroupBy = true;
                }
            }

            groupByList.forEach((groupBy) -> groupBy.setStreamId(referenceName));

            selector.addGroupByList(groupByList);

            List selectorList;
            if (!isQueryGroupBySameAsAggGroupBy) {
                selectorList = constructSelectorList(isProcessingOnExternalTime, isDistributed, isLatestEventColAdded,
                        baseAggregatorBeginIndex, groupByVariablesList.size(), finalBaseExpressionsList,
                        tableDefinition, groupByList);
            } else {
                selectorList = defaultSelectorList;
            }

            for (OutputAttribute outputAttribute : selectorList) {
                if (outputAttribute.getExpression() instanceof Variable) {
                    ((Variable) outputAttribute.getExpression()).setStreamId(referenceName);
                } else {
                    for (Expression parameter :
                            ((AttributeFunction) outputAttribute.getExpression()).getParameters()) {
                        ((Variable) parameter).setStreamId(referenceName);
                    }
                }
            }
            selector.addSelectionList(selectorList);

            try {
                aggregationTables.entrySet().forEach(
                        (durationTableEntry -> {
                            CompiledSelection compiledSelection = ((QueryableProcessor) durationTableEntry.getValue())
                                    .compileSelection(
                                            selector, tableDefinition.getAttributeList(), metaInfoHolderForTableLookups,
                                            variableExpExecutorsForTableLookups, tableMap, siddhiQueryContext
                                    );
                            withinTableCompiledSelection.put(durationTableEntry.getKey(), compiledSelection);
                        })
                );
            } catch (SiddhiAppCreationException | SiddhiAppValidationException | QueryableRecordTableException e) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Aggregation Query optimization failed for aggregation: '" + aggregationName + "'. " +
                            "Creating table lookup query in normal mode. Reason for failure: " + e.getMessage(), e);
                }
                isOptimisedTableLookup = false;
            }

        }

        for (Map.Entry entry : aggregationTables.entrySet()) {
            CompiledCondition withinTableCompileCondition = entry.getValue().compileCondition(withinExpressionTable,
                    metaInfoHolderForTableLookups, variableExpExecutorsForTableLookups, tableMap,
                    siddhiQueryContext);
            withinTableCompiledConditions.put(entry.getKey(), withinTableCompileCondition);
        }

        // Create compile condition for in-memory data.
        // This compile condition is used to check whether the running aggregates (in-memory data)
        // are within given duration
        withinInMemoryCompileCondition = OperatorParser.constructOperator(new ComplexEventChunk<>(),
                withinExpression, metaInfoHolderForTableLookups, variableExpExecutorsForTableLookups, tableMap,
                siddhiQueryContext);

        // Create compile condition for in-memory data, in case of distributed
        // Look at the lower level granularities
        Map withinTableLowerGranularityCompileCondition = new HashMap<>();
        Expression lowerGranularity;
        if (isDistributed) {
            for (int i = 0; i < lowerGranularitySize; i++) {
                if (isProcessingOnExternalTime) {
                    lowerGranularity = Expression.and(
                            Expression.compare(
                                    Expression.variable("AGG_TIMESTAMP"),
                                    Compare.Operator.GREATER_THAN_EQUAL,
                                    Expression.variable(lowerGranularityAttributes.get(i))),
                            withinExpressionTable
                    );
                } else {
                    if (shouldApplyReducedCondition) {
                        lowerGranularity = Expression.and(
                                Expression.compare(
                                        Expression.variable("AGG_TIMESTAMP"),
                                        Compare.Operator.GREATER_THAN_EQUAL,
                                        Expression.variable(lowerGranularityAttributes.get(i))),
                                reducedExpression
                        );
                    } else {
                        lowerGranularity =
                                Expression.compare(
                                        Expression.variable("AGG_TIMESTAMP"),
                                        Compare.Operator.GREATER_THAN_EQUAL,
                                        Expression.variable(lowerGranularityAttributes.get(i)));
                    }
                }
                TimePeriod.Duration duration = this.activeIncrementalDurations.get(i);
                String tableName = aggregationName + "_" + duration.toString();
                CompiledCondition compiledCondition = tableMap.get(tableName).compileCondition(lowerGranularity,
                        metaInfoHolderForTableLookups, variableExpExecutorsForTableLookups, tableMap,
                        siddhiQueryContext);
                withinTableLowerGranularityCompileCondition.put(duration, compiledCondition);
            }
        }

        QueryParserHelper.reduceMetaComplexEvent(metaInfoHolderForTableLookups.getMetaStateEvent());

        // On compile condition.
        // After finding all the aggregates belonging to within duration, the final on condition (given as
        // "on stream1.name == aggregator.nickName ..." in the join query) must be executed on that data.
        // This condition is used for that purpose.
        onCompiledCondition = OperatorParser.constructOperator(new ComplexEventChunk<>(), expression,
                matchingMetaInfoHolder, variableExpressionExecutors, tableMap, siddhiQueryContext);

        return new IncrementalAggregateCompileCondition(isOnDemandQuery, aggregationName, isProcessingOnExternalTime,
                isDistributed, activeIncrementalDurations, aggregationTables, outputExpressionExecutors,
                isOptimisedTableLookup, withinTableCompiledSelection, withinTableCompiledConditions,
                withinInMemoryCompileCondition, withinTableLowerGranularityCompileCondition, onCompiledCondition,
                additionalAttributes, perExpressionExecutor, startTimeEndTimeExpressionExecutor,
                timestampFilterExecutors, aggregateMetaSteamEvent, matchingMetaInfoHolder,
                metaInfoHolderForTableLookups, variableExpExecutorsForTableLookups);

    }

    public void startPurging() {
        incrementalDataPurger.executeIncrementalDataPurging();
    }

    public void initialiseExecutors(boolean isFirstEventArrived) {
        // State only updated when first event arrives to IncrementalAggregationProcessor
        if (isFirstEventArrived) {
            this.isFirstEventArrived = true;
            for (Map.Entry durationIncrementalExecutorEntry :
                    this.incrementalExecutorMap.entrySet()) {
                if (activeIncrementalDurations.contains(durationIncrementalExecutorEntry.getKey())) {
                    if (durationIncrementalExecutorEntry.getValue() instanceof IncrementalExecutor) {
                        ((IncrementalExecutor) durationIncrementalExecutorEntry.getValue()).setProcessingExecutor(true);
                    } else {
                        ((PersistedIncrementalExecutor) durationIncrementalExecutorEntry.getValue())
                                .setProcessingExecutor(true);
                    }
                }
            }
        }
        this.incrementalExecutorsInitialiser.initialiseExecutors();
    }

    public void processEvents(ComplexEventChunk streamEventComplexEventChunk) {
        incrementalExecutorMap.get(incrementalDurations.get(0)).execute(streamEventComplexEventChunk);
    }

    public Map> getAggregationDurationExecutorMap() {
        return aggregationDurationExecutorMap;
    }

    public void setAggregationDurationExecutorMap(Map> aggregationDurationExecutorMap) {
        this.aggregationDurationExecutorMap = aggregationDurationExecutorMap;
    }

    public IncrementalExecutorsInitialiser getIncrementalExecutorsInitialiser() {
        return incrementalExecutorsInitialiser;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy