/* *************************************************************************************** * 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.expression.baseagg; import com.espertech.esper.client.EventBean; import com.espertech.esper.core.service.StatementType; import com.espertech.esper.epl.agg.service.AggregationMethodFactory; import com.espertech.esper.epl.agg.service.AggregationResultFuture; import com.espertech.esper.epl.expression.core.*; import com.espertech.esper.metrics.instrumentation.InstrumentationHelper; import com.espertech.esper.util.JavaClassHelper; import java.io.StringWriter; /** * Base expression node that represents an aggregation function such as 'sum' or 'count'. * <p> * In terms of validation each concrete aggregation node must implement it's own validation. * <p> * In terms of evaluation this base class will ask the assigned {@link com.espertech.esper.epl.agg.service.AggregationResultFuture} for the current state, * using a column number assigned to the node. * <p> * Concrete subclasses must supply an aggregation state prototype node {@link com.espertech.esper.epl.agg.aggregator.AggregationMethod} that reflects * each group's (there may be group-by critera) current aggregation state. */ public abstract class ExprAggregateNodeBase extends ExprNodeBase implements ExprEvaluator, ExprAggregateNode { private static final long serialVersionUID = 4859196214837888423L; protected transient AggregationResultFuture aggregationResultFuture; protected int column; private transient AggregationMethodFactory aggregationMethodFactory; protected ExprAggregateLocalGroupByDesc optionalAggregateLocalGroupByDesc; protected ExprNode optionalFilter; protected ExprNode[] positionalParams; /** * Indicator for whether the aggregation is distinct - i.e. only unique values are considered. */ protected boolean isDistinct; /** * Returns the aggregation function name for representation in a generate expression string. * * @return aggregation function name */ public abstract String getAggregationFunctionName(); protected abstract boolean isFilterExpressionAsLastParameter(); /** * Return true if a expression aggregate node semantically equals the current node, or false if not. * <p>For use by the equalsNode implementation which compares the distinct flag. * * @param node to compare to * @return true if semantically equal, or false if not equals */ protected abstract boolean equalsNodeAggregateMethodOnly(ExprAggregateNode node); /** * Gives the aggregation node a chance to validate the sub-expression types. * * @param validationContext validation information * @return aggregation function factory to use * @throws com.espertech.esper.epl.expression.core.ExprValidationException when expression validation failed */ protected abstract AggregationMethodFactory validateAggregationChild(ExprValidationContext validationContext) throws ExprValidationException; /** * Ctor. * * @param distinct - sets the flag indicatating whether only unique values should be aggregated */ protected ExprAggregateNodeBase(boolean distinct) { isDistinct = distinct; } public ExprNode[] getPositionalParams() { return positionalParams; } public ExprEvaluator getExprEvaluator() { return this; } public boolean isConstantResult() { return false; } public ExprNode validate(ExprValidationContext validationContext) throws ExprValidationException { validatePositionals(); aggregationMethodFactory = validateAggregationChild(validationContext); if (validationContext.getExprEvaluatorContext().getStatementType() == StatementType.CREATE_TABLE && (optionalAggregateLocalGroupByDesc != null || optionalFilter != null)) { throw new ExprValidationException("The 'group_by' and 'filter' parameter is not allowed in create-table statements"); } return null; } public void validatePositionals() throws ExprValidationException { ExprAggregateNodeParamDesc paramDesc = ExprAggregateNodeUtil.getValidatePositionalParams(this.getChildNodes(), !(this instanceof ExprAggregationPlugInNodeMarker)); this.optionalAggregateLocalGroupByDesc = paramDesc.getOptLocalGroupBy(); this.optionalFilter = paramDesc.getOptionalFilter(); if (optionalAggregateLocalGroupByDesc != null) { ExprNodeUtility.validateNoSpecialsGroupByExpressions(optionalAggregateLocalGroupByDesc.getPartitionExpressions()); } if (optionalFilter != null) { ExprNodeUtility.validateNoSpecialsGroupByExpressions(new ExprNode[] {optionalFilter}); } if (optionalFilter != null && isFilterExpressionAsLastParameter()) { if (paramDesc.getPositionalParams().length > 1) { throw new ExprValidationException("Only a single filter expression can be provided"); } positionalParams = ExprNodeUtility.addExpression(paramDesc.getPositionalParams(), optionalFilter); } else { positionalParams = paramDesc.getPositionalParams(); } } public Class getType() { if (aggregationMethodFactory == null) { throw new IllegalStateException("Aggregation method has not been set"); } return aggregationMethodFactory.getResultType(); } /** * Returns the aggregation state factory for use in grouping aggregation states per group-by keys. * * @return prototype aggregation state as a factory for aggregation states per group-by key value */ public AggregationMethodFactory getFactory() { if (aggregationMethodFactory == null) { throw new IllegalStateException("Aggregation method has not been set"); } return aggregationMethodFactory; } /** * Assigns to the node the future which can be queried for the current aggregation state at evaluation time. * * @param aggregationResultFuture - future containing state * @param column - column to hand to future for easy access */ public void setAggregationResultFuture(AggregationResultFuture aggregationResultFuture, int column) { this.aggregationResultFuture = aggregationResultFuture; this.column = column; } public final Object evaluate(EventBean[] events, boolean isNewData, ExprEvaluatorContext exprEvaluatorContext) { if (InstrumentationHelper.ENABLED) { Object value = aggregationResultFuture.getValue(column, exprEvaluatorContext.getAgentInstanceId(), events, isNewData, exprEvaluatorContext); InstrumentationHelper.get().qaExprAggValue(this, value); return value; } return aggregationResultFuture.getValue(column, exprEvaluatorContext.getAgentInstanceId(), events, isNewData, exprEvaluatorContext); } /** * Returns true if the aggregation node is only aggregatig distinct values, or false if * aggregating all values. * * @return true if 'distinct' keyword was given, false if not */ public boolean isDistinct() { return isDistinct; } public final boolean equalsNode(ExprNode node, boolean ignoreStreamPrefix) { if (!(node instanceof ExprAggregateNode)) { return false; } ExprAggregateNode other = (ExprAggregateNode) node; if (other.isDistinct() != this.isDistinct) { return false; } return this.equalsNodeAggregateMethodOnly(other); } /** * For use by implementing classes, validates the aggregation node expecting * a single numeric-type child node. * * @param hasFilter for filter indication * @return numeric type of single child * @throws com.espertech.esper.epl.expression.core.ExprValidationException if the validation failed */ protected final Class validateNumericChildAllowFilter(boolean hasFilter) throws ExprValidationException { if (positionalParams.length == 0 || positionalParams.length > 2) { throw makeExceptionExpectedParamNum(1, 2); } // validate child expression (filter expression is actually always the first expression) ExprNode child = positionalParams[0]; if (hasFilter) { validateFilter(positionalParams[1].getExprEvaluator()); } Class childType = child.getExprEvaluator().getType(); if (!JavaClassHelper.isNumeric(childType)) { throw new ExprValidationException("Implicit conversion from datatype '" + (childType == null ? "null" : childType.getSimpleName()) + "' to numeric is not allowed for aggregation function '" + getAggregationFunctionName() + "'"); } return childType; } protected ExprValidationException makeExceptionExpectedParamNum(int lower, int upper) { String message = "The '" + getAggregationFunctionName() + "' function expects "; if (lower == 0 && upper == 0) { message += "no parameters"; } else if (lower == upper) { message += lower + " parameters"; } else { message += "at least " + lower + " and up to " + upper + " parameters"; } return new ExprValidationException(message); } public void toPrecedenceFreeEPL(StringWriter writer) { writer.append(getAggregationFunctionName()); writer.append('('); if (isDistinct) { writer.append("distinct "); } if (this.getChildNodes().length > 0) { this.getChildNodes()[0].toEPL(writer, getPrecedence()); String delimiter = ","; for (int i = 1; i < this.getChildNodes().length; i++) { writer.write(delimiter); delimiter = ","; this.getChildNodes()[i].toEPL(writer, getPrecedence()); } } else { if (isExprTextWildcardWhenNoParams()) { writer.append('*'); } } writer.append(')'); } public ExprPrecedenceEnum getPrecedence() { return ExprPrecedenceEnum.MINIMUM; } public void validateFilter(ExprEvaluator filterEvaluator) throws ExprValidationException { if (JavaClassHelper.getBoxedType(filterEvaluator.getType()) != Boolean.class) { throw new ExprValidationException("Invalid filter expression parameter to the aggregation function '" + getAggregationFunctionName() + "' is expected to return a boolean value but returns " + JavaClassHelper.getClassNameFullyQualPretty(filterEvaluator.getType())); } } public ExprAggregateLocalGroupByDesc getOptionalLocalGroupBy() { return optionalAggregateLocalGroupByDesc; } public ExprNode getOptionalFilter() { return optionalFilter; } protected boolean isExprTextWildcardWhenNoParams() { return true; } }