/* *************************************************************************************** * 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.rollup; import com.espertech.esper.collection.CombinationEnumeration; import com.espertech.esper.collection.MultiKeyInt; import com.espertech.esper.epl.expression.core.ExprNode; import com.espertech.esper.epl.expression.core.ExprNodeUtility; import com.espertech.esper.epl.expression.core.ExprValidationException; import com.espertech.esper.epl.expression.visitor.ExprNodeSubselectDeclaredDotVisitor; import com.espertech.esper.epl.spec.*; import com.espertech.esper.util.CollectionUtil; import com.espertech.esper.util.SerializableObjectCopier; import java.io.StringWriter; import java.util.*; public class GroupByExpressionHelper { public static GroupByClauseExpressions getGroupByRollupExpressions(List<GroupByClauseElement> groupByElements, SelectClauseSpecRaw selectClauseSpec, ExprNode optionalHavingNode, List<OrderByItem> orderByList, ExprNodeSubselectDeclaredDotVisitor visitor) throws ExprValidationException { if (groupByElements == null || groupByElements.size() == 0) { return null; } // walk group-by-elements, determine group-by expressions and rollup nodes GroupByExpressionInfo groupByExpressionInfo = groupByToRollupNodes(groupByElements); // obtain expression nodes, collect unique nodes and assign index List<ExprNode> distinctGroupByExpressions = new ArrayList<ExprNode>(); Map<ExprNode, Integer> expressionToIndex = new HashMap<ExprNode, Integer>(); for (ExprNode exprNode : groupByExpressionInfo.getExpressions()) { boolean found = false; for (int i = 0; i < distinctGroupByExpressions.size(); i++) { ExprNode other = distinctGroupByExpressions.get(i); // find same expression if (ExprNodeUtility.deepEquals(exprNode, other, false)) { expressionToIndex.put(exprNode, i); found = true; break; } } // not seen before if (!found) { expressionToIndex.put(exprNode, distinctGroupByExpressions.size()); distinctGroupByExpressions.add(exprNode); } } // determine rollup, validate it is either (not both) boolean hasGroupingSet = false; boolean hasRollup = false; for (GroupByClauseElement element : groupByElements) { if (element instanceof GroupByClauseElementGroupingSet) { hasGroupingSet = true; } if (element instanceof GroupByClauseElementRollupOrCube) { hasRollup = true; } } // no-rollup or grouping-sets means simply validate ExprNode[] groupByExpressions = distinctGroupByExpressions.toArray(new ExprNode[distinctGroupByExpressions.size()]); if (!hasRollup && !hasGroupingSet) { return new GroupByClauseExpressions(groupByExpressions); } // evaluate rollup node roots List<GroupByRollupNodeBase> nodes = groupByExpressionInfo.getNodes(); Object[][] perNodeCombinations = new Object[nodes.size()][]; GroupByRollupEvalContext context = new GroupByRollupEvalContext(expressionToIndex); try { for (int i = 0; i < nodes.size(); i++) { GroupByRollupNodeBase node = nodes.get(i); List<int[]> combinations = node.evaluate(context); perNodeCombinations[i] = new Object[combinations.size()]; for (int j = 0; j < combinations.size(); j++) { perNodeCombinations[i][j] = combinations.get(j); } } } catch (GroupByRollupDuplicateException ex) { if (ex.getIndexes().length == 0) { throw new ExprValidationException("Failed to validate the group-by clause, found duplicate specification of the overall grouping '()'"); } else { StringWriter writer = new StringWriter(); String delimiter = ""; for (int i = 0; i < ex.getIndexes().length; i++) { writer.append(delimiter); writer.append(ExprNodeUtility.toExpressionStringMinPrecedenceSafe(groupByExpressions[ex.getIndexes()[i]])); delimiter = ", "; } throw new ExprValidationException("Failed to validate the group-by clause, found duplicate specification of expressions (" + writer.toString() + ")"); } } // enumerate combinations building an index list CombinationEnumeration combinationEnumeration = new CombinationEnumeration(perNodeCombinations); Set<Integer> combination = new TreeSet<Integer>(); Set<MultiKeyInt> indexList = new LinkedHashSet<MultiKeyInt>(); for (; combinationEnumeration.hasMoreElements(); ) { combination.clear(); Object[] combinationOA = combinationEnumeration.nextElement(); for (Object indexes : combinationOA) { int[] indexarr = (int[]) indexes; for (int anIndex : indexarr) { combination.add(anIndex); } } int[] indexArr = CollectionUtil.intArray(combination); indexList.add(new MultiKeyInt(indexArr)); } // obtain rollup levels int[][] rollupLevels = new int[indexList.size()][]; int count = 0; for (MultiKeyInt mk : indexList) { rollupLevels[count++] = mk.getKeys(); } int numberOfLevels = rollupLevels.length; if (numberOfLevels == 1 && rollupLevels[0].length == 0) { throw new ExprValidationException("Failed to validate the group-by clause, the overall grouping '()' cannot be the only grouping"); } // obtain select-expression copies for rewrite List<SelectClauseElementRaw> expressions = selectClauseSpec.getSelectExprList(); ExprNode[][] selects = new ExprNode[numberOfLevels][]; for (int i = 0; i < numberOfLevels; i++) { selects[i] = new ExprNode[expressions.size()]; for (int j = 0; j < expressions.size(); j++) { SelectClauseElementRaw selectRaw = expressions.get(j); if (!(selectRaw instanceof SelectClauseExprRawSpec)) { throw new ExprValidationException("Group-by with rollup requires that the select-clause does not use wildcard"); } SelectClauseExprRawSpec compiled = (SelectClauseExprRawSpec) selectRaw; selects[i][j] = copyVisitExpression(compiled.getSelectExpression(), visitor); } } // obtain having-expression copies for rewrite ExprNode[] optHavingNodeCopy = null; if (optionalHavingNode != null) { optHavingNodeCopy = new ExprNode[numberOfLevels]; for (int i = 0; i < numberOfLevels; i++) { optHavingNodeCopy[i] = copyVisitExpression(optionalHavingNode, visitor); } } // obtain orderby-expression copies for rewrite ExprNode[][] optOrderByCopy = null; if (orderByList != null && orderByList.size() > 0) { optOrderByCopy = new ExprNode[numberOfLevels][]; for (int i = 0; i < numberOfLevels; i++) { optOrderByCopy[i] = new ExprNode[orderByList.size()]; for (int j = 0; j < orderByList.size(); j++) { OrderByItem element = orderByList.get(j); optOrderByCopy[i][j] = copyVisitExpression(element.getExprNode(), visitor); } } } return new GroupByClauseExpressions(groupByExpressions, rollupLevels, selects, optHavingNodeCopy, optOrderByCopy); } private static GroupByExpressionInfo groupByToRollupNodes(List<GroupByClauseElement> groupByExpressions) { List<GroupByRollupNodeBase> parents = new ArrayList<GroupByRollupNodeBase>(groupByExpressions.size()); List<ExprNode> exprNodes = new ArrayList<ExprNode>(); for (GroupByClauseElement element : groupByExpressions) { GroupByRollupNodeBase parent; if (element instanceof GroupByClauseElementExpr) { GroupByClauseElementExpr expr = (GroupByClauseElementExpr) element; exprNodes.add(expr.getExpr()); parent = new GroupByRollupNodeSingleExpr(expr.getExpr()); } else if (element instanceof GroupByClauseElementRollupOrCube) { GroupByClauseElementRollupOrCube spec = (GroupByClauseElementRollupOrCube) element; parent = new GroupByRollupNodeRollupOrCube(spec.isCube()); groupByAddRollup(spec, parent, exprNodes); } else if (element instanceof GroupByClauseElementGroupingSet) { GroupByClauseElementGroupingSet spec = (GroupByClauseElementGroupingSet) element; parent = new GroupByRollupNodeGroupingSet(); for (GroupByClauseElement groupElement : spec.getElements()) { if (groupElement instanceof GroupByClauseElementExpr) { GroupByClauseElementExpr single = (GroupByClauseElementExpr) groupElement; exprNodes.add(single.getExpr()); parent.add(new GroupByRollupNodeSingleExpr(single.getExpr())); } if (groupElement instanceof GroupByClauseElementCombinedExpr) { GroupByClauseElementCombinedExpr combined = (GroupByClauseElementCombinedExpr) groupElement; exprNodes.addAll(combined.getExpressions()); parent.add(new GroupByRollupNodeCombinedExpr(combined.getExpressions())); } if (groupElement instanceof GroupByClauseElementRollupOrCube) { GroupByClauseElementRollupOrCube rollup = (GroupByClauseElementRollupOrCube) groupElement; GroupByRollupNodeRollupOrCube node = new GroupByRollupNodeRollupOrCube(rollup.isCube()); groupByAddRollup(rollup, node, exprNodes); parent.add(node); } } } else { throw new IllegalStateException("Unexpected group-by clause element " + element); } parents.add(parent); } return new GroupByExpressionInfo(exprNodes, parents); } private static void groupByAddRollup(GroupByClauseElementRollupOrCube spec, GroupByRollupNodeBase parent, List<ExprNode> exprNodes) { for (GroupByClauseElement rolledUp : spec.getRollupExpressions()) { if (rolledUp instanceof GroupByClauseElementExpr) { GroupByClauseElementExpr expr = (GroupByClauseElementExpr) rolledUp; exprNodes.add(expr.getExpr()); parent.add(new GroupByRollupNodeSingleExpr(expr.getExpr())); } else { GroupByClauseElementCombinedExpr combined = (GroupByClauseElementCombinedExpr) rolledUp; exprNodes.addAll(combined.getExpressions()); parent.add(new GroupByRollupNodeCombinedExpr(combined.getExpressions())); } } } private static ExprNode copyVisitExpression(ExprNode expression, ExprNodeSubselectDeclaredDotVisitor visitor) { try { ExprNode node = (ExprNode) SerializableObjectCopier.copy(expression); node.accept(visitor); return node; } catch (Exception e) { throw new RuntimeException("Internal error providing expression tree: " + e.getMessage(), e); } } private static class GroupByExpressionInfo { private final List<ExprNode> expressions; private final List<GroupByRollupNodeBase> nodes; private GroupByExpressionInfo(List<ExprNode> expressions, List<GroupByRollupNodeBase> nodes) { this.expressions = expressions; this.nodes = nodes; } public List<ExprNode> getExpressions() { return expressions; } public List<GroupByRollupNodeBase> getNodes() { return nodes; } } }