/* *************************************************************************************** * 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.filter; import com.espertech.esper.client.annotation.Hint; import com.espertech.esper.client.annotation.HintEnum; import com.espertech.esper.collection.CombinationEnumeration; import com.espertech.esper.epl.expression.baseagg.ExprAggregateNode; import com.espertech.esper.epl.expression.baseagg.ExprAggregateNodeUtil; import com.espertech.esper.epl.expression.core.ExprNode; import com.espertech.esper.epl.expression.core.ExprNodeUtility; import com.espertech.esper.epl.expression.core.ExprValidationContext; import com.espertech.esper.epl.expression.core.ExprValidationException; import com.espertech.esper.epl.expression.ops.ExprAndNode; import com.espertech.esper.epl.expression.ops.ExprOrNode; import com.espertech.esper.epl.expression.subquery.ExprSubselectNode; import com.espertech.esper.epl.expression.visitor.ExprNodeSubselectDeclaredDotVisitor; import com.espertech.esper.epl.expression.visitor.ExprNodeTableAccessFinderVisitor; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; public class FilterSpecCompilerPlanner { protected static List<FilterSpecParam>[] planFilterParameters(List<ExprNode> validatedNodes, FilterSpecCompilerArgs args) throws ExprValidationException { if (validatedNodes.isEmpty()) { return allocateListArray(0); } FilterParamExprMap filterParamExprMap = new FilterParamExprMap(); // Make filter parameter for each expression node, if it can be optimized decomposePopulateConsolidate(filterParamExprMap, validatedNodes, args); // Use all filter parameter and unassigned expressions List<FilterSpecParam> filterParams = new ArrayList<FilterSpecParam>(); filterParams.addAll(filterParamExprMap.getFilterParams()); int countUnassigned = filterParamExprMap.countUnassignedExpressions(); // we are done if there are no remaining nodes if (countUnassigned == 0) { return allocateListArraySizeOne(filterParams); } // determine max-width int filterServiceMaxFilterWidth = args.configurationInformation.getEngineDefaults().getExecution().getFilterServiceMaxFilterWidth(); Hint hint = HintEnum.MAX_FILTER_WIDTH.getHint(args.annotations); if (hint != null) { String hintValue = HintEnum.MAX_FILTER_WIDTH.getHintAssignedValue(hint); filterServiceMaxFilterWidth = Integer.parseInt(hintValue); } List<FilterSpecParam>[] plan = null; if (filterServiceMaxFilterWidth > 0) { plan = planRemainingNodesIfFeasible(filterParamExprMap, args, filterServiceMaxFilterWidth); } if (plan != null) { return plan; } // handle no-plan FilterSpecParamExprNode node = makeRemainingNode(filterParamExprMap.getUnassignedExpressions(), args); filterParams.add(node); return allocateListArraySizeOne(filterParams); } private static List<FilterSpecParam>[] planRemainingNodesIfFeasible(FilterParamExprMap overallExpressions, FilterSpecCompilerArgs args, int filterServiceMaxFilterWidth) throws ExprValidationException { List<ExprNode> unassigned = overallExpressions.getUnassignedExpressions(); List<ExprOrNode> orNodes = new ArrayList<ExprOrNode>(unassigned.size()); for (ExprNode node : unassigned) { if (node instanceof ExprOrNode) { orNodes.add((ExprOrNode) node); } } FilterParamExprMap expressionsWithoutOr = new FilterParamExprMap(); expressionsWithoutOr.add(overallExpressions); // first dimension: or-node index // second dimension: or child node index FilterParamExprMap[][] orNodesMaps = new FilterParamExprMap[orNodes.size()][]; int countOr = 0; int sizeFactorized = 1; int[] sizePerOr = new int[orNodes.size()]; for (ExprOrNode orNode : orNodes) { expressionsWithoutOr.removeNode(orNode); orNodesMaps[countOr] = new FilterParamExprMap[orNode.getChildNodes().length]; int len = orNode.getChildNodes().length; for (int i = 0; i < len; i++) { FilterParamExprMap map = new FilterParamExprMap(); orNodesMaps[countOr][i] = map; List<ExprNode> nodes = Collections.singletonList(orNode.getChildNodes()[i]); decomposePopulateConsolidate(map, nodes, args); } sizePerOr[countOr] = len; sizeFactorized = sizeFactorized * len; countOr++; } // we become too large if (sizeFactorized > filterServiceMaxFilterWidth) { return null; } // combine List<FilterSpecParam>[] result = (List<FilterSpecParam>[]) new List[sizeFactorized]; CombinationEnumeration permutations = CombinationEnumeration.fromZeroBasedRanges(sizePerOr); int count = 0; for (; permutations.hasMoreElements(); ) { Object[] permutation = permutations.nextElement(); result[count] = computePermutation(expressionsWithoutOr, permutation, orNodesMaps, args); count++; } return result; } private static List<FilterSpecParam> computePermutation(FilterParamExprMap filterParamExprMap, Object[] permutation, FilterParamExprMap[][] orNodesMaps, FilterSpecCompilerArgs args) throws ExprValidationException { FilterParamExprMap mapAll = new FilterParamExprMap(); mapAll.add(filterParamExprMap); // combine for (int orNodeNum = 0; orNodeNum < permutation.length; orNodeNum++) { int orChildNodeNum = (Integer) permutation[orNodeNum]; FilterParamExprMap mapOrSub = orNodesMaps[orNodeNum][orChildNodeNum]; mapAll.add(mapOrSub); } // consolidate across FilterSpecCompilerConsolidateUtil.consolidate(mapAll, args.statementName); List<FilterSpecParam> filterParams = new ArrayList<FilterSpecParam>(); filterParams.addAll(mapAll.getFilterParams()); int countUnassigned = mapAll.countUnassignedExpressions(); if (countUnassigned == 0) { return filterParams; } FilterSpecParamExprNode node = makeRemainingNode(mapAll.getUnassignedExpressions(), args); filterParams.add(node); return filterParams; } private static void decomposePopulateConsolidate(FilterParamExprMap filterParamExprMap, List<ExprNode> validatedNodes, FilterSpecCompilerArgs args) throws ExprValidationException { List<ExprNode> constituents = decomposeCheckAggregation(validatedNodes); // Make filter parameter for each expression node, if it can be optimized for (ExprNode constituent : constituents) { FilterSpecParam param = FilterSpecCompilerMakeParamUtil.makeFilterParam(constituent, args.arrayEventTypes, args.exprEvaluatorContext, args.statementName); filterParamExprMap.put(constituent, param); // accepts null values as the expression may not be optimized } // Consolidate entries as possible, i.e. (a != 5 and a != 6) is (a not in (5,6)) // Removes duplicates for same property and same filter operator for filter service index optimizations FilterSpecCompilerConsolidateUtil.consolidate(filterParamExprMap, args.statementName); } private static FilterSpecParamExprNode makeRemainingNode(List<ExprNode> unassignedExpressions, FilterSpecCompilerArgs args) throws ExprValidationException { if (unassignedExpressions.isEmpty()) { throw new IllegalArgumentException(); } // any unoptimized expression nodes are put under one AND ExprNode exprNode; if (unassignedExpressions.size() == 1) { exprNode = unassignedExpressions.get(0); } else { exprNode = makeValidateAndNode(unassignedExpressions, args); } return makeBooleanExprParam(exprNode, args); } private static List<FilterSpecParam>[] allocateListArraySizeOne(List<FilterSpecParam> params) { List<FilterSpecParam>[] arr = allocateListArray(1); arr[0] = params; return arr; } private static List<FilterSpecParam>[] allocateListArray(int i) { return (List<FilterSpecParam>[]) new List[i]; } private static FilterSpecParamExprNode makeBooleanExprParam(ExprNode exprNode, FilterSpecCompilerArgs args) { boolean hasSubselectFilterStream = determineSubselectFilterStream(exprNode); boolean hasTableAccess = determineTableAccessFilterStream(exprNode); FilterSpecLookupable lookupable = new FilterSpecLookupable(FilterSpecCompiler.PROPERTY_NAME_BOOLEAN_EXPRESSION, null, exprNode.getExprEvaluator().getType(), false); return new FilterSpecParamExprNode(lookupable, FilterOperator.BOOLEAN_EXPRESSION, exprNode, args.taggedEventTypes, args.arrayEventTypes, args.variableService, args.tableService, args.eventAdapterService, args.filterBooleanExpressionFactory, args.configurationInformation, hasSubselectFilterStream, hasTableAccess); } private static ExprAndNode makeValidateAndNode(List<ExprNode> remainingExprNodes, FilterSpecCompilerArgs args) throws ExprValidationException { ExprAndNode andNode = ExprNodeUtility.connectExpressionsByLogicalAnd(remainingExprNodes); ExprValidationContext validationContext = new ExprValidationContext(args.streamTypeService, args.engineImportService, args.statementExtensionSvcContext, null, args.timeProvider, args.variableService, args.tableService, args.exprEvaluatorContext, args.eventAdapterService, args.statementName, args.statementId, args.annotations, args.contextDescriptor, false, false, true, false, null, false); andNode.validate(validationContext); return andNode; } private static boolean determineTableAccessFilterStream(ExprNode exprNode) { ExprNodeTableAccessFinderVisitor visitor = new ExprNodeTableAccessFinderVisitor(); exprNode.accept(visitor); return visitor.isHasTableAccess(); } private static boolean determineSubselectFilterStream(ExprNode exprNode) { ExprNodeSubselectDeclaredDotVisitor visitor = new ExprNodeSubselectDeclaredDotVisitor(); exprNode.accept(visitor); if (visitor.getSubselects().isEmpty()) { return false; } for (ExprSubselectNode subselectNode : visitor.getSubselects()) { if (subselectNode.isFilterStreamSubselect()) { return true; } } return false; } private static List<ExprNode> decomposeCheckAggregation(List<ExprNode> validatedNodes) throws ExprValidationException { // Break a top-level AND into constituent expression nodes List<ExprNode> constituents = new ArrayList<ExprNode>(); for (ExprNode validated : validatedNodes) { if (validated instanceof ExprAndNode) { recursiveAndConstituents(constituents, validated); } else { constituents.add(validated); } // Ensure there is no aggregation nodes List<ExprAggregateNode> aggregateExprNodes = new LinkedList<ExprAggregateNode>(); ExprAggregateNodeUtil.getAggregatesBottomUp(validated, aggregateExprNodes); if (!aggregateExprNodes.isEmpty()) { throw new ExprValidationException("Aggregation functions not allowed within filters"); } } return constituents; } private static void recursiveAndConstituents(List<ExprNode> constituents, ExprNode exprNode) { for (ExprNode inner : exprNode.getChildNodes()) { if (inner instanceof ExprAndNode) { recursiveAndConstituents(constituents, inner); } else { constituents.add(inner); } } } }