/************************************************************************************** * Copyright (C) 2008 EsperTech, Inc. All rights reserved. * * http://esper.codehaus.org * * 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.client.EventType; import com.espertech.esper.client.annotation.Hint; import com.espertech.esper.client.annotation.HintEnum; import com.espertech.esper.epl.agg.access.AggregationAccessor; import com.espertech.esper.epl.agg.access.AggregationAccessorSlotPair; import com.espertech.esper.epl.expression.*; import com.espertech.esper.epl.variable.VariableService; import com.espertech.esper.util.CollectionUtil; import java.lang.annotation.Annotation; import java.util.*; /** * Factory for aggregation service instances. * <p> * Consolidates aggregation nodes such that result futures point to a single instance and * no re-evaluation of the same result occurs. */ public class AggregationServiceFactoryFactory { /** * Produces an aggregation service for use with match-recognice. * * @param numStreams number of streams * @param measureExprNodesPerStream measure nodes * @param exprEvaluatorContext context for expression evaluatiom * @return service */ public static AggregationServiceMatchRecognizeFactoryDesc getServiceMatchRecognize(int numStreams, Map<Integer, List<ExprAggregateNode>> measureExprNodesPerStream, ExprEvaluatorContext exprEvaluatorContext) { Map<Integer, List<AggregationServiceAggExpressionDesc>> equivalencyListPerStream = new TreeMap<Integer, List<AggregationServiceAggExpressionDesc>>(); for (Map.Entry<Integer, List<ExprAggregateNode>> entry : measureExprNodesPerStream.entrySet()) { List<AggregationServiceAggExpressionDesc> equivalencyList = new ArrayList<AggregationServiceAggExpressionDesc>(); equivalencyListPerStream.put(entry.getKey(), equivalencyList); for (ExprAggregateNode selectAggNode : entry.getValue()) { addEquivalent(selectAggNode, equivalencyList); } } LinkedHashMap<Integer, AggregationMethodFactory[]> aggregatorsPerStream = new LinkedHashMap<Integer, AggregationMethodFactory[]>(); Map<Integer, ExprEvaluator[]> evaluatorsPerStream = new HashMap<Integer, ExprEvaluator[]>(); for (Map.Entry<Integer, List<AggregationServiceAggExpressionDesc>> equivalencyPerStream : equivalencyListPerStream.entrySet()) { int index = 0; int stream = equivalencyPerStream.getKey(); AggregationMethodFactory[] aggregators = new AggregationMethodFactory[equivalencyPerStream.getValue().size()]; aggregatorsPerStream.put(stream, aggregators); ExprEvaluator[] evaluators = new ExprEvaluator[equivalencyPerStream.getValue().size()]; evaluatorsPerStream.put(stream, evaluators); for (AggregationServiceAggExpressionDesc aggregation : equivalencyPerStream.getValue()) { ExprAggregateNode aggregateNode = aggregation.getAggregationNode(); if (aggregateNode.getChildNodes().size() > 1) { evaluators[index] = getMultiNodeEvaluator(aggregateNode.getChildNodes(), exprEvaluatorContext); } else if (!aggregateNode.getChildNodes().isEmpty()) { // Use the evaluation node under the aggregation node to obtain the aggregation value evaluators[index] = aggregateNode.getChildNodes().get(0).getExprEvaluator(); } // For aggregation that doesn't evaluate any particular sub-expression, return null on evaluation else { evaluators[index] = new ExprEvaluator() { public Object evaluate(EventBean[] eventsPerStream, boolean isNewData, ExprEvaluatorContext exprEvaluatorContext) { return null; } public Class getType() { return null; } public Map<String, Object> getEventType() { return null; } }; } aggregators[index] = aggregateNode.getFactory(); index++; } } // Assign a column number to each aggregation node. The regular aggregation goes first followed by access-aggregation. int columnNumber = 0; List<AggregationServiceAggExpressionDesc> allExpressions = new ArrayList<AggregationServiceAggExpressionDesc>(); for (Map.Entry<Integer, List<AggregationServiceAggExpressionDesc>> equivalencyPerStream : equivalencyListPerStream.entrySet()) { for (AggregationServiceAggExpressionDesc entry : equivalencyPerStream.getValue()) { entry.setColumnNum(columnNumber++); } allExpressions.addAll(equivalencyPerStream.getValue()); } AggregationServiceMatchRecognizeFactory factory = new AggregationServiceMatchRecognizeFactoryImpl(numStreams, aggregatorsPerStream, evaluatorsPerStream); return new AggregationServiceMatchRecognizeFactoryDesc(factory, allExpressions); } /** * Returns an instance to handle the aggregation required by the aggregation expression nodes, depending on * whether there are any group-by nodes. * * * @param selectAggregateExprNodes - aggregation nodes extracted out of the select expression * @param havingAggregateExprNodes - aggregation nodes extracted out of the select expression * @param orderByAggregateExprNodes - aggregation nodes extracted out of the select expression * @param hasGroupByClause - indicator on whethere there is group-by required, or group-all * @param exprEvaluatorContext context for expression evaluatiom * @param annotations - statement annotations * @param variableService - variable * @param isJoin - true for joins * @param whereClause the where-clause function if any * @param havingClause the having-clause function if any * @return instance for aggregation handling * @throws com.espertech.esper.epl.expression.ExprValidationException if validation fails */ public static AggregationServiceFactoryDesc getService(List<ExprAggregateNode> selectAggregateExprNodes, List<ExprAggregateNode> havingAggregateExprNodes, List<ExprAggregateNode> orderByAggregateExprNodes, boolean hasGroupByClause, ExprEvaluatorContext exprEvaluatorContext, Annotation[] annotations, VariableService variableService, boolean isJoin, ExprNode whereClause, ExprNode havingClause, AggregationServiceFactoryService factoryService, EventType[] typesPerStream) throws ExprValidationException { // No aggregates used, we do not need this service if ((selectAggregateExprNodes.isEmpty()) && (havingAggregateExprNodes.isEmpty())) { return new AggregationServiceFactoryDesc(factoryService.getNullAggregationService(), Collections.<AggregationServiceAggExpressionDesc>emptyList()); } // Validate the absence of "prev" function in where-clause: // Since the "previous" function does not post remove stream results, disallow when used with aggregations. if ((whereClause != null) || (havingClause != null)) { ExprNodePreviousVisitorWParent visitor = new ExprNodePreviousVisitorWParent(); if (whereClause != null) { whereClause.accept(visitor); } if (havingClause != null) { havingClause.accept(visitor); } if ((visitor.getPrevious() != null) && (!visitor.getPrevious().isEmpty())) { String funcname = visitor.getPrevious().get(0).getSecond().getPreviousType().toString().toLowerCase(); throw new ExprValidationException("The '" + funcname + "' function may not occur in the where-clause or having-clause of a statement with aggregations as 'previous' does not provide remove stream data; Use the 'first','last','window' or 'count' aggregation functions instead"); } } // Compile a map of aggregation nodes and equivalent-to aggregation nodes. // Equivalent-to functions are for example "select sum(a*b), 5*sum(a*b)". // Reducing the total number of aggregation functions. List<AggregationServiceAggExpressionDesc> aggregations = new ArrayList<AggregationServiceAggExpressionDesc>(); for (ExprAggregateNode selectAggNode : selectAggregateExprNodes) { addEquivalent(selectAggNode, aggregations); } for (ExprAggregateNode havingAggNode : havingAggregateExprNodes) { addEquivalent(havingAggNode, aggregations); } for (ExprAggregateNode orderByAggNode : orderByAggregateExprNodes) { addEquivalent(orderByAggNode, aggregations); } // Assign a column number to each aggregation node. The regular aggregation goes first followed by access-aggregation. int columnNumber = 0; for (AggregationServiceAggExpressionDesc entry : aggregations) { if (entry.getFactory().getSpec(false) == null) { entry.setColumnNum(columnNumber++); } } for (AggregationServiceAggExpressionDesc entry : aggregations) { if (entry.getFactory().getSpec(false) != null) { entry.setColumnNum(columnNumber++); } } // handle regular aggregation (function provides value(s) to aggregate) List<AggregationMethodFactory> aggregators = new ArrayList<AggregationMethodFactory>(); List<ExprEvaluator> evaluators = new ArrayList<ExprEvaluator>(); // handle accessor aggregation (direct data window by-group access to properties) Map<Integer, Integer> streamSlots = new TreeMap<Integer, Integer>(); List<AggregationAccessorSlotPair> accessorPairs = new ArrayList<AggregationAccessorSlotPair>(); // Construct a list of evaluation node for the aggregation functions (regular agg). // For example "sum(2 * 3)" would make the sum an evaluation node. // Also determine all the streams that need direct access and compute a index (slot) for each (access agg). int currentSlot = 0; for (AggregationServiceAggExpressionDesc aggregation : aggregations) { ExprAggregateNode aggregateNode = aggregation.getAggregationNode(); if (aggregateNode.getFactory().getSpec(false) == null) { ExprEvaluator evaluator; if (aggregateNode.getChildNodes().size() > 1) { evaluator = getMultiNodeEvaluator(aggregateNode.getChildNodes(), exprEvaluatorContext); } else if (!aggregateNode.getChildNodes().isEmpty()) { if (aggregateNode.getChildNodes().get(0) instanceof ExprNumberSetWildcardMarker) { final Class returnType = typesPerStream != null && typesPerStream.length > 0 ? typesPerStream[0].getUnderlyingType() : null; if (isJoin || returnType == null) { throw new ExprValidationException("Invalid use of wildcard (*) for stream selection in a join or an empty from-clause, please use the stream-alias syntax to select a specific stream instead"); } evaluator = new ExprEvaluator() { public Object evaluate(EventBean[] eventsPerStream, boolean isNewData, ExprEvaluatorContext exprEvaluatorContext) { return eventsPerStream[0].getUnderlying(); } public Class getType() { return returnType; } public Map<String, Object> getEventType() { return null; } }; } else { // Use the evaluation node under the aggregation node to obtain the aggregation value evaluator = aggregateNode.getChildNodes().get(0).getExprEvaluator(); } } // For aggregation that doesn't evaluate any particular sub-expression, return null on evaluation else { evaluator = new ExprEvaluator() { public Object evaluate(EventBean[] eventsPerStream, boolean isNewData, ExprEvaluatorContext exprEvaluatorContext) { return null; } public Class getType() { return null; } public Map<String, Object> getEventType() { return null; } }; } AggregationMethodFactory aggregator = aggregateNode.getFactory(); evaluators.add(evaluator); aggregators.add(aggregator); } else { AggregationSpec spec = aggregateNode.getFactory().getSpec(false); AggregationAccessor accessor = aggregateNode.getFactory().getAccessor(); Integer slot = streamSlots.get(spec.getStreamNum()); if (slot == null) { streamSlots.put(spec.getStreamNum(), currentSlot); slot = currentSlot++; } accessorPairs.add(new AggregationAccessorSlotPair(slot, accessor)); } } // handle no group-by clause cases ExprEvaluator[] evaluatorsArr = evaluators.toArray(new ExprEvaluator[evaluators.size()]); AggregationMethodFactory[] aggregatorsArr = aggregators.toArray(new AggregationMethodFactory[aggregators.size()]); AggregationAccessorSlotPair[] pairs = accessorPairs.toArray(new AggregationAccessorSlotPair[accessorPairs.size()]); int[] accessedStreams = CollectionUtil.intArray(streamSlots.keySet()); AggregationServiceFactory serviceFactory; // Handle without a group-by clause: we group all into the same pot if (!hasGroupByClause) { if ((evaluatorsArr.length > 0) && (accessorPairs.isEmpty())) { serviceFactory = factoryService.getNoGroupNoAccess(evaluatorsArr, aggregatorsArr); } else if ((evaluatorsArr.length == 0) && (!accessorPairs.isEmpty())) { serviceFactory = factoryService.getNoGroupAccessOnly(pairs, accessedStreams, isJoin); } else { serviceFactory = factoryService.getNoGroupAccessMixed(evaluatorsArr, aggregatorsArr, pairs, accessedStreams, isJoin); } } else { boolean hasNoReclaim = HintEnum.DISABLE_RECLAIM_GROUP.getHint(annotations) != null; Hint reclaimGroupAged = HintEnum.RECLAIM_GROUP_AGED.getHint(annotations); Hint reclaimGroupFrequency = HintEnum.RECLAIM_GROUP_AGED.getHint(annotations); if (hasNoReclaim) { if ((evaluatorsArr.length > 0) && (accessorPairs.isEmpty())) { serviceFactory = factoryService.getGroupedNoReclaimNoAccess(evaluatorsArr, aggregatorsArr); } else if ((evaluatorsArr.length == 0) && (!accessorPairs.isEmpty())) { serviceFactory = factoryService.getGroupNoReclaimAccessOnly(pairs, accessedStreams, isJoin); } else { serviceFactory = factoryService.getGroupNoReclaimMixed(evaluatorsArr, aggregatorsArr, pairs, accessedStreams, isJoin); } } else if (reclaimGroupAged != null) { serviceFactory = factoryService.getGroupReclaimAged(evaluatorsArr, aggregatorsArr, reclaimGroupAged, reclaimGroupFrequency, variableService, pairs, accessedStreams, isJoin); } else { if ((evaluatorsArr.length > 0) && (accessorPairs.isEmpty())) { serviceFactory = factoryService.getGroupReclaimNoAccess(evaluatorsArr, aggregatorsArr, pairs, accessedStreams, isJoin); } else { serviceFactory = factoryService.getGroupReclaimMixable(evaluatorsArr, aggregatorsArr, pairs, accessedStreams, isJoin); } } } return new AggregationServiceFactoryDesc(serviceFactory, aggregations); } private static ExprEvaluator getMultiNodeEvaluator(List<ExprNode> childNodes, ExprEvaluatorContext exprEvaluatorContext) { final int size = childNodes.size(); final List<ExprNode> exprNodes = childNodes; final Object[] prototype = new Object[size]; // determine constant nodes int count = 0; for (ExprNode node : exprNodes) { if (node.isConstantResult()) { prototype[count] = node.getExprEvaluator().evaluate(null, true, exprEvaluatorContext); } count++; } return new ExprEvaluator() { public Object evaluate(EventBean[] eventsPerStream, boolean isNewData, ExprEvaluatorContext exprEvaluatorContext) { int count = 0; for (ExprNode node : exprNodes) { prototype[count] = node.getExprEvaluator().evaluate(eventsPerStream, isNewData, exprEvaluatorContext); count++; } return prototype; } public Class getType() { return Object[].class; } public Map<String, Object> getEventType() { return null; } }; } private static void addEquivalent(ExprAggregateNode aggNodeToAdd, List<AggregationServiceAggExpressionDesc> equivalencyList) { // Check any same aggregation nodes among all aggregation clauses boolean foundEquivalent = false; for (AggregationServiceAggExpressionDesc existing : equivalencyList) { ExprAggregateNode aggNode = existing.getAggregationNode(); if (ExprNodeUtility.deepEquals(aggNode, aggNodeToAdd)) { existing.addEquivalent(aggNodeToAdd); foundEquivalent = true; break; } } if (!foundEquivalent) { equivalencyList.add(new AggregationServiceAggExpressionDesc(aggNodeToAdd, aggNodeToAdd.getFactory())); } } }