/************************************************************************************** * 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.filter; import com.espertech.esper.client.ConfigurationInformation; import com.espertech.esper.client.EventType; import com.espertech.esper.collection.Pair; import com.espertech.esper.core.context.util.ContextDescriptor; import com.espertech.esper.core.service.ExprEvaluatorContextStatement; import com.espertech.esper.core.service.StatementContext; import com.espertech.esper.core.start.EPStatementStartMethodHelperValidate; import com.espertech.esper.epl.core.MethodResolutionService; import com.espertech.esper.epl.core.StreamTypeService; import com.espertech.esper.epl.core.StreamTypeServiceImpl; import com.espertech.esper.epl.core.ViewResourceDelegateUnverified; import com.espertech.esper.epl.expression.*; import com.espertech.esper.epl.named.NamedWindowProcessor; import com.espertech.esper.epl.property.PropertyEvaluator; import com.espertech.esper.epl.property.PropertyEvaluatorFactory; import com.espertech.esper.epl.spec.*; import com.espertech.esper.epl.variable.VariableService; import com.espertech.esper.event.EventAdapterService; import com.espertech.esper.event.property.IndexedProperty; import com.espertech.esper.event.property.NestedProperty; import com.espertech.esper.event.property.Property; import com.espertech.esper.event.property.PropertyParser; import com.espertech.esper.schedule.TimeProvider; import com.espertech.esper.type.RelationalOpEnum; import com.espertech.esper.util.JavaClassHelper; import com.espertech.esper.util.SimpleNumberCoercer; import com.espertech.esper.util.SimpleNumberCoercerFactory; import com.espertech.esper.view.ViewFactoryChain; import com.espertech.esper.view.ViewProcessingException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.StringWriter; import java.lang.annotation.Annotation; import java.lang.reflect.Array; import java.util.*; /** * Helper to compile (validate and optimize) filter expressions as used in pattern and filter-based streams. */ public final class FilterSpecCompiler { private static final Log log = LogFactory.getLog(FilterSpecCompiler.class); /** * Assigned for filter parameters that are based on boolean expression and not on * any particular property name. * <p> * Keeping this artificial property name is a simplification as optimized filter parameters * generally keep a property name. */ public final static String PROPERTY_NAME_BOOLEAN_EXPRESSION = ".boolean_expression"; /** * Factory method for compiling filter expressions into a filter specification * for use with filter service. * @param eventType is the filtered-out event type * @param eventTypeName is the name of the event type * @param filterExpessions is a list of filter expressions * @param taggedEventTypes is a map of stream names (tags) and event types available * @param arrayEventTypes is a map of name tags and event type per tag for repeat-expressions that generate an array of events * @param streamTypeService is used to set rules for resolving properties * @param optionalStreamName - the stream name, if provided * @param optionalPropertyEvalSpec - specification for evaluating properties * @param statementContext context for statement * @return compiled filter specification * @throws ExprValidationException if the expression or type validations failed */ public static FilterSpecCompiled makeFilterSpec(EventType eventType, String eventTypeName, List<ExprNode> filterExpessions, PropertyEvalSpec optionalPropertyEvalSpec, LinkedHashMap<String, Pair<EventType, String>> taggedEventTypes, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes, StreamTypeService streamTypeService, String optionalStreamName, StatementContext statementContext, Collection<Integer> assignedTypeNumberStack) throws ExprValidationException { // Validate all nodes, make sure each returns a boolean and types are good; // Also decompose all AND super nodes into individual expressions List<ExprNode> validatedNodes = validateAllowSubquery(filterExpessions, streamTypeService, statementContext, taggedEventTypes, arrayEventTypes); return build(validatedNodes, eventType, eventTypeName, optionalPropertyEvalSpec, taggedEventTypes, arrayEventTypes, streamTypeService, optionalStreamName, statementContext, assignedTypeNumberStack); } public static FilterSpecCompiled build(List<ExprNode> validatedNodes, EventType eventType, String eventTypeName, PropertyEvalSpec optionalPropertyEvalSpec, LinkedHashMap<String, Pair<EventType, String>> taggedEventTypes, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes, StreamTypeService streamTypeService, String optionalStreamName, StatementContext stmtContext, Collection<Integer> assignedTypeNumberStack) throws ExprValidationException { ExprEvaluatorContextStatement evaluatorContextStmt = new ExprEvaluatorContextStatement(stmtContext); return buildNoStmtCtx(validatedNodes, eventType, eventTypeName, optionalPropertyEvalSpec, taggedEventTypes, arrayEventTypes, streamTypeService, optionalStreamName, assignedTypeNumberStack, evaluatorContextStmt, stmtContext.getStatementId(), stmtContext.getStatementName(), stmtContext.getAnnotations(), stmtContext.getContextDescriptor(), stmtContext.getMethodResolutionService(), stmtContext.getEventAdapterService(), stmtContext.getTimeProvider(), stmtContext.getVariableService(), stmtContext.getConfigSnapshot()); } public static FilterSpecCompiled buildNoStmtCtx(List<ExprNode> validatedNodes, EventType eventType, String eventTypeName, PropertyEvalSpec optionalPropertyEvalSpec, LinkedHashMap<String, Pair<EventType, String>> taggedEventTypes, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes, StreamTypeService streamTypeService, String optionalStreamName, Collection<Integer> assignedTypeNumberStack, ExprEvaluatorContext exprEvaluatorContext, String statementId, String statementName, Annotation[] annotations, ContextDescriptor contextDescriptor, MethodResolutionService methodResolutionService, EventAdapterService eventAdapterService, TimeProvider timeProvider, VariableService variableService, ConfigurationInformation configurationInformation) throws ExprValidationException { List<ExprNode> constituents = decomposeCheckAggregation(validatedNodes); // From the constituents make a filter specification FilterParamExprMap filterParamExprMap = new FilterParamExprMap(); // Make filter parameter for each expression node, if it can be optimized for (ExprNode constituent : constituents) { FilterSpecParam param = makeFilterParam(constituent, taggedEventTypes, arrayEventTypes, exprEvaluatorContext, 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 consolidate(filterParamExprMap, statementName); // Use all filter parameter and unassigned expressions List<FilterSpecParam> filterParams = new ArrayList<FilterSpecParam>(); filterParams.addAll(filterParamExprMap.getFilterParams()); List<ExprNode> remainingExprNodes = filterParamExprMap.getUnassignedExpressions(); // any unoptimized expression nodes are put under one AND ExprNode exprNode = null; if (!remainingExprNodes.isEmpty()) { if (remainingExprNodes.size() == 1) { exprNode = remainingExprNodes.get(0); } else { ExprAndNode andNode = ExprNodeUtility.connectExpressionsByLogicalAnd(remainingExprNodes); ExprValidationContext validationContext = new ExprValidationContext(streamTypeService, methodResolutionService, null, timeProvider, variableService, exprEvaluatorContext, eventAdapterService, statementName, statementId, annotations, contextDescriptor); andNode.validate(validationContext); exprNode = andNode; } } // if there are boolean expressions, add if (exprNode != null) { boolean hasSubselectFilterStream = determineSubselectFilterStream(exprNode); FilterSpecLookupable lookupable = new FilterSpecLookupable(PROPERTY_NAME_BOOLEAN_EXPRESSION, null, exprNode.getExprEvaluator().getType()); FilterSpecParamExprNode param = new FilterSpecParamExprNode(lookupable, FilterOperator.BOOLEAN_EXPRESSION, exprNode, taggedEventTypes, arrayEventTypes, variableService, eventAdapterService, configurationInformation, statementName, hasSubselectFilterStream); filterParams.add(param); } PropertyEvaluator optionalPropertyEvaluator = null; if (optionalPropertyEvalSpec != null) { optionalPropertyEvaluator = PropertyEvaluatorFactory.makeEvaluator(optionalPropertyEvalSpec, eventType, optionalStreamName, eventAdapterService, methodResolutionService, timeProvider, variableService, streamTypeService.getEngineURIQualifier(), statementId, statementName, annotations, assignedTypeNumberStack, configurationInformation); } FilterSpecCompiled spec = new FilterSpecCompiled(eventType, eventTypeName, filterParams, optionalPropertyEvaluator); if (log.isDebugEnabled()) { log.debug(".makeFilterSpec spec=" + spec); } return spec; } 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; } // remove duplicate propertyName + filterOperator items making a judgement to optimize or simply remove the optimized form private static void consolidate(List<FilterSpecParam> items, FilterParamExprMap filterParamExprMap, String statementName) { FilterOperator op = items.get(0).getFilterOperator(); if (op == FilterOperator.NOT_EQUAL) { handleConsolidateNotEqual(items, filterParamExprMap, statementName); } else { // for all others we simple remove the second optimized form (filter param with same prop name and filter op) // and thus the boolean expression that started this is included for (int i = 1; i < items.size(); i++) { filterParamExprMap.removeValue(items.get(i)); } } } /** * Validates expression nodes and returns a list of validated nodes. * @param exprNodes is the nodes to validate * @param streamTypeService is provding type information for each stream * @param taggedEventTypes pattern tagged types * @param arrayEventTypes @return list of validated expression nodes * @return expr nodes * @param statementContext context * @throws ExprValidationException for validation errors */ public static List<ExprNode> validateAllowSubquery(List<ExprNode> exprNodes, StreamTypeService streamTypeService, StatementContext statementContext, LinkedHashMap<String, Pair<EventType, String>> taggedEventTypes, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes) throws ExprValidationException { List<ExprNode> validatedNodes = new ArrayList<ExprNode>(); ExprEvaluatorContextStatement evaluatorContextStmt = new ExprEvaluatorContextStatement(statementContext); ExprValidationContext validationContext = new ExprValidationContext(streamTypeService, statementContext.getMethodResolutionService(), null, statementContext.getTimeProvider(), statementContext.getVariableService(), evaluatorContextStmt, statementContext.getEventAdapterService(), statementContext.getStatementName(), statementContext.getStatementId(), statementContext.getAnnotations(), statementContext.getContextDescriptor()); for (ExprNode node : exprNodes) { // Determine subselects ExprNodeSubselectDeclaredDotVisitor visitor = new ExprNodeSubselectDeclaredDotVisitor(); node.accept(visitor); // Compile subselects if (!visitor.getSubselects().isEmpty()) { // The outer event type is the filtered-type itself int subselectStreamNumber = 2048; for (ExprSubselectNode subselect : visitor.getSubselects()) { subselectStreamNumber++; handleSubselectSelectClauses(subselectStreamNumber, statementContext, subselect, streamTypeService.getEventTypes()[0], streamTypeService.getStreamNames()[0], streamTypeService.getStreamNames()[0], taggedEventTypes, arrayEventTypes); } } ExprNode validated = ExprNodeUtility.getValidatedSubtree(node, validationContext); validatedNodes.add(validated); if ((validated.getExprEvaluator().getType() != Boolean.class) && ((validated.getExprEvaluator().getType() != boolean.class))) { throw new ExprValidationException("Filter expression not returning a boolean value: '" + validated.toExpressionString() + "'"); } } return validatedNodes; } private static void handleSubselectSelectClauses(int subselectStreamNumber, StatementContext statementContext, ExprSubselectNode subselect, EventType outerEventType, String outerEventTypeName, String outerStreamName, LinkedHashMap<String, Pair<EventType, String>> taggedEventTypes, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes) throws ExprValidationException { StatementSpecCompiled statementSpec = subselect.getStatementSpecCompiled(); StreamSpecCompiled filterStreamSpec = statementSpec.getStreamSpecs().get(0); ViewFactoryChain viewFactoryChain; String subselecteventTypeName = null; // construct view factory chain try { if (statementSpec.getStreamSpecs().get(0) instanceof FilterStreamSpecCompiled) { FilterStreamSpecCompiled filterStreamSpecCompiled = (FilterStreamSpecCompiled) statementSpec.getStreamSpecs().get(0); subselecteventTypeName = filterStreamSpecCompiled.getFilterSpec().getFilterForEventTypeName(); // A child view is required to limit the stream if (filterStreamSpec.getViewSpecs().size() == 0) { throw new ExprValidationException("Subqueries require one or more views to limit the stream, consider declaring a length or time window"); } // Register filter, create view factories viewFactoryChain = statementContext.getViewService().createFactories(subselectStreamNumber, filterStreamSpecCompiled.getFilterSpec().getResultEventType(), filterStreamSpec.getViewSpecs(), filterStreamSpec.getOptions(), statementContext); subselect.setRawEventType(viewFactoryChain.getEventType()); } else { NamedWindowConsumerStreamSpec namedSpec = (NamedWindowConsumerStreamSpec) statementSpec.getStreamSpecs().get(0); NamedWindowProcessor processor = statementContext.getNamedWindowService().getProcessor(namedSpec.getWindowName()); viewFactoryChain = statementContext.getViewService().createFactories(0, processor.getNamedWindowType(), namedSpec.getViewSpecs(), namedSpec.getOptions(), statementContext); subselecteventTypeName = namedSpec.getWindowName(); } } catch (ViewProcessingException ex) { throw new ExprValidationException("Error validating subexpression: " + ex.getMessage(), ex); } // the final event type EventType eventType = viewFactoryChain.getEventType(); // determine a stream name unless one was supplied String subexpressionStreamName = filterStreamSpec.getOptionalStreamName(); if (subexpressionStreamName == null) { subexpressionStreamName = "$subselect_" + subselectStreamNumber; } // Named windows don't allow data views if (filterStreamSpec instanceof NamedWindowConsumerStreamSpec) { EPStatementStartMethodHelperValidate.validateNoDataWindowOnNamedWindow(viewFactoryChain.getViewFactoryChain()); } // Streams event types are the original stream types with the stream zero the subselect stream LinkedHashMap<String, Pair<EventType, String>> namesAndTypes = new LinkedHashMap<String, Pair<EventType, String>>(); namesAndTypes.put(subexpressionStreamName, new Pair<EventType, String>(eventType, subselecteventTypeName)); namesAndTypes.put(outerStreamName, new Pair<EventType, String>(outerEventType, outerEventTypeName)); if (taggedEventTypes != null) { for (Map.Entry<String, Pair<EventType, String>> entry : taggedEventTypes.entrySet()) { namesAndTypes.put(entry.getKey(), new Pair<EventType, String>(entry.getValue().getFirst(), entry.getValue().getSecond())); } } if (arrayEventTypes != null) { for (Map.Entry<String, Pair<EventType, String>> entry : arrayEventTypes.entrySet()) { namesAndTypes.put(entry.getKey(), new Pair<EventType, String>(entry.getValue().getFirst(), entry.getValue().getSecond())); } } StreamTypeService subselectTypeService = new StreamTypeServiceImpl(namesAndTypes, statementContext.getEngineURI(), true, true); ViewResourceDelegateUnverified viewResourceDelegateSubselect = new ViewResourceDelegateUnverified(); subselect.setFilterSubqueryStreamTypes(subselectTypeService); // Validate select expression SelectClauseSpecCompiled selectClauseSpec = subselect.getStatementSpecCompiled().getSelectClauseSpec(); if (selectClauseSpec.getSelectExprList().size() > 0) { if (selectClauseSpec.getSelectExprList().size() > 1) { throw new ExprValidationException("Subquery multi-column select is not allowed in this context."); } SelectClauseElementCompiled element = selectClauseSpec.getSelectExprList().get(0); if (element instanceof SelectClauseExprCompiledSpec) { // validate SelectClauseExprCompiledSpec compiled = (SelectClauseExprCompiledSpec) element; ExprNode selectExpression = compiled.getSelectExpression(); ExprEvaluatorContextStatement evaluatorContextStmt = new ExprEvaluatorContextStatement(statementContext); ExprValidationContext validationContext = new ExprValidationContext(subselectTypeService, statementContext.getMethodResolutionService(), viewResourceDelegateSubselect, statementContext.getSchedulingService(), statementContext.getVariableService(), evaluatorContextStmt, statementContext.getEventAdapterService(), statementContext.getStatementName(), statementContext.getStatementId(), statementContext.getAnnotations(), statementContext.getContextDescriptor()); selectExpression = ExprNodeUtility.getValidatedSubtree(selectExpression, validationContext); subselect.setSelectClause(new ExprNode[] {selectExpression}); subselect.setSelectAsNames(new String[] {compiled.getAssignedName()}); // handle aggregation List<ExprAggregateNode> aggExprNodes = new LinkedList<ExprAggregateNode>(); ExprAggregateNodeUtil.getAggregatesBottomUp(selectExpression, aggExprNodes); if (aggExprNodes.size() > 0) { // Other stream properties, if there is aggregation, cannot be under aggregation. for (ExprAggregateNode aggNode : aggExprNodes) { List<Pair<Integer, String>> propertiesNodesAggregated = ExprNodeUtility.getExpressionProperties(aggNode, true); for (Pair<Integer, String> pair : propertiesNodesAggregated) { if (pair.getFirst() != 0) { throw new ExprValidationException("Subselect aggregation function cannot aggregate across correlated properties"); } } } // This stream (stream 0) properties must either all be under aggregation, or all not be. List<Pair<Integer, String>> propertiesNotAggregated = ExprNodeUtility.getExpressionProperties(selectExpression, false); for (Pair<Integer, String> pair : propertiesNotAggregated) { if (pair.getFirst() == 0) { throw new ExprValidationException("Subselect properties must all be within aggregation functions"); } } } } } } public 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 consolidate(FilterParamExprMap filterParamExprMap, String statementName) { // consolidate or place in a boolean expression (by removing filter spec param from the map) // any filter parameter that feature the same property name and filter operator, // i.e. we are looking for "a!=5 and a!=6" to transform to "a not in (5,6)" which can match faster // considering that "a not in (5,6) and a not in (7,8)" is "a not in (5, 6, 7, 8)" therefore // we need to consolidate until there is no more work to do Map<Pair<FilterSpecLookupable, FilterOperator>, List<FilterSpecParam>> mapOfParams = new HashMap<Pair<FilterSpecLookupable, FilterOperator>, List<FilterSpecParam>>(); boolean haveConsolidated; do { haveConsolidated = false; mapOfParams.clear(); // sort into buckets of propertyName + filterOperator combination for (FilterSpecParam currentParam : filterParamExprMap.getFilterParams()) { FilterSpecLookupable lookupable = currentParam.getLookupable(); FilterOperator op = currentParam.getFilterOperator(); Pair<FilterSpecLookupable, FilterOperator> key = new Pair<FilterSpecLookupable, FilterOperator>(lookupable, op); List<FilterSpecParam> existingParam = mapOfParams.get(key); if (existingParam == null) { existingParam = new ArrayList<FilterSpecParam>(); mapOfParams.put(key, existingParam); } existingParam.add(currentParam); } for (List<FilterSpecParam> entry : mapOfParams.values()) { if (entry.size() > 1) { haveConsolidated = true; consolidate(entry, filterParamExprMap, statementName); } } } while(haveConsolidated); } // consolidate "val != 3 and val != 4 and val != 5" // to "val not in (3, 4, 5)" private static void handleConsolidateNotEqual(List<FilterSpecParam> parameters, FilterParamExprMap filterParamExprMap, String statementName) { List<FilterSpecParamInValue> values = new ArrayList<FilterSpecParamInValue>(); ExprNode lastNotEqualsExprNode = null; for (FilterSpecParam param : parameters) { if (param instanceof FilterSpecParamConstant) { FilterSpecParamConstant constantParam = (FilterSpecParamConstant) param; Object constant = constantParam.getFilterConstant(); values.add(new InSetOfValuesConstant(constant)); } else if (param instanceof FilterSpecParamEventProp) { FilterSpecParamEventProp eventProp = (FilterSpecParamEventProp) param; values.add(new InSetOfValuesEventProp(eventProp.getResultEventAsName(), eventProp.getResultEventProperty(), eventProp.isMustCoerce(), JavaClassHelper.getBoxedType(eventProp.getCoercionType()))); } else if (param instanceof FilterSpecParamEventPropIndexed) { FilterSpecParamEventPropIndexed eventProp = (FilterSpecParamEventPropIndexed) param; values.add(new InSetOfValuesEventPropIndexed(eventProp.getResultEventAsName(), eventProp.getResultEventIndex(), eventProp.getResultEventProperty(), eventProp.isMustCoerce(), JavaClassHelper.getBoxedType(eventProp.getCoercionType()), statementName)); } else { throw new IllegalArgumentException("Unknown filter parameter:" + param.toString()); } lastNotEqualsExprNode = filterParamExprMap.removeEntry(param); } FilterSpecParamIn param = new FilterSpecParamIn(parameters.get(0).getLookupable(), FilterOperator.NOT_IN_LIST_OF_VALUES, values); filterParamExprMap.put(lastNotEqualsExprNode, param); } /** * For a given expression determine if this is optimizable and create the filter parameter * representing the expression, or null if not optimizable. * @param constituent is the expression to look at * @param taggedEventTypes event types and their tags * @param arrayEventTypes @return filter parameter representing the expression, or null * @throws ExprValidationException if the expression is invalid * @return FilterSpecParam filter param */ protected static FilterSpecParam makeFilterParam(ExprNode constituent, LinkedHashMap<String, Pair<EventType, String>> taggedEventTypes, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes, ExprEvaluatorContext exprEvaluatorContext, String statementName) throws ExprValidationException { // Is this expresson node a simple compare, i.e. a=5 or b<4; these can be indexed if ((constituent instanceof ExprEqualsNode) || (constituent instanceof ExprRelationalOpNode)) { FilterSpecParam param = handleEqualsAndRelOp(constituent, arrayEventTypes, exprEvaluatorContext, statementName); if (param != null) { return param; } } // Is this expresson node a simple compare, i.e. a=5 or b<4; these can be indexed if (constituent instanceof ExprInNode) { FilterSpecParam param = handleInSetNode((ExprInNode)constituent, arrayEventTypes, exprEvaluatorContext, statementName); if (param != null) { return param; } } if (constituent instanceof ExprBetweenNode) { FilterSpecParam param = handleRangeNode((ExprBetweenNode)constituent, arrayEventTypes, exprEvaluatorContext, statementName); if (param != null) { return param; } } if (constituent instanceof ExprPlugInSingleRowNode) { FilterSpecParam param = handlePlugInSingleRow((ExprPlugInSingleRowNode) constituent, exprEvaluatorContext, statementName); if (param != null) { return param; } } return null; } private static FilterSpecParam handlePlugInSingleRow(ExprPlugInSingleRowNode constituent, ExprEvaluatorContext exprEvaluatorContext, String statementName) { if (JavaClassHelper.getBoxedType(constituent.getExprEvaluator().getType()) != Boolean.class) { return null; } if (!constituent.getFilterLookupEligible()) { return null; } FilterSpecLookupable lookupable = constituent.getFilterLookupable(); return new FilterSpecParamConstant(lookupable, FilterOperator.EQUAL, true); } private static FilterSpecParam handleRangeNode(ExprBetweenNode betweenNode, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes, ExprEvaluatorContext exprEvaluatorContext, String statementName) { ExprNode left = betweenNode.getChildNodes().get(0); if (left instanceof ExprFilterOptimizableNode) { ExprFilterOptimizableNode filterOptimizableNode = (ExprFilterOptimizableNode) left; FilterSpecLookupable lookupable = filterOptimizableNode.getFilterLookupable(); FilterOperator op = FilterOperator.parseRangeOperator(betweenNode.isLowEndpointIncluded(), betweenNode.isHighEndpointIncluded(), betweenNode.isNotBetween()); FilterSpecParamRangeValue low = handleRangeNodeEndpoint(betweenNode.getChildNodes().get(1), arrayEventTypes, exprEvaluatorContext, statementName); FilterSpecParamRangeValue high = handleRangeNodeEndpoint(betweenNode.getChildNodes().get(2), arrayEventTypes, exprEvaluatorContext, statementName); if ((low != null) && (high != null)) { return new FilterSpecParamRange(lookupable, op, low, high); } } return null; } private static FilterSpecParamRangeValue handleRangeNodeEndpoint(ExprNode endpoint, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes, ExprEvaluatorContext exprEvaluatorContext, String statementName) { // constant if (ExprNodeUtility.isConstantValueExpr(endpoint)) { ExprConstantNode node = (ExprConstantNode) endpoint; Object value = node.evaluate(null, true, exprEvaluatorContext); if (value instanceof String) { return new RangeValueString((String)value); } else { return new RangeValueDouble(((Number)value).doubleValue()); } } if (endpoint instanceof ExprContextPropertyNode) { ExprContextPropertyNode node = (ExprContextPropertyNode) endpoint; return new RangeValueContextProp(node.getGetter()); } // or property if (endpoint instanceof ExprIdentNode) { ExprIdentNode identNodeInner = (ExprIdentNode) endpoint; if (identNodeInner.getStreamId() == 0) { return null; } if (arrayEventTypes != null && !arrayEventTypes.isEmpty() && arrayEventTypes.containsKey(identNodeInner.getResolvedStreamName())) { Pair<Integer, String> indexAndProp = getStreamIndex(identNodeInner.getResolvedPropertyName()); return new RangeValueEventPropIndexed(identNodeInner.getResolvedStreamName(), indexAndProp.getFirst(), indexAndProp.getSecond(), statementName); } else { return new RangeValueEventProp(identNodeInner.getResolvedStreamName(), identNodeInner.getResolvedPropertyName()); } } return null; } private static FilterSpecParam handleInSetNode(ExprInNode constituent, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes, ExprEvaluatorContext exprEvaluatorContext, String statementName) throws ExprValidationException { ExprNode left = constituent.getChildNodes().get(0); if (!(left instanceof ExprFilterOptimizableNode)) { return null; } ExprFilterOptimizableNode filterOptimizableNode = (ExprFilterOptimizableNode) left; FilterSpecLookupable lookupable = filterOptimizableNode.getFilterLookupable(); FilterOperator op = FilterOperator.IN_LIST_OF_VALUES; if (constituent.isNotIn()) { op = FilterOperator.NOT_IN_LIST_OF_VALUES; } int expectedNumberOfConstants = constituent.getChildNodes().size() - 1; List<FilterSpecParamInValue> listofValues = new ArrayList<FilterSpecParamInValue>(); Iterator<ExprNode> it = constituent.getChildNodes().iterator(); it.next(); // ignore the first node as it's the identifier while (it.hasNext()) { ExprNode subNode = it.next(); if (ExprNodeUtility.isConstantValueExpr(subNode)) { ExprConstantNode constantNode = (ExprConstantNode) subNode; Object constant = constantNode.evaluate(null, true, exprEvaluatorContext); if (constant instanceof Collection) { return null; } if (constant instanceof Map) { return null; } if ((constant != null) && (constant.getClass().isArray())) { for (int i = 0; i < Array.getLength(constant); i++) { Object arrayElement = Array.get(constant, i); Object arrayElementCoerced = handleConstantsCoercion(lookupable, arrayElement); listofValues.add(new InSetOfValuesConstant(arrayElementCoerced)); if (i > 0) { expectedNumberOfConstants++; } } } else { constant = handleConstantsCoercion(lookupable, constant); listofValues.add(new InSetOfValuesConstant(constant)); } } if (subNode instanceof ExprContextPropertyNode) { ExprContextPropertyNode contextPropertyNode = (ExprContextPropertyNode) subNode; Class returnType = contextPropertyNode.getType(); if (JavaClassHelper.isImplementsInterface(contextPropertyNode.getType(), Collection.class) || JavaClassHelper.isImplementsInterface(contextPropertyNode.getType(), Map.class)) { return null; } if ((returnType != null) && (returnType.getClass().isArray())) { return null; } SimpleNumberCoercer coercer = getNumberCoercer(left.getExprEvaluator().getType(), contextPropertyNode.getType(), lookupable.getExpression()); listofValues.add(new InSetOfValuesContextProp(contextPropertyNode.getPropertyName(), contextPropertyNode.getGetter(), coercer)); } if (subNode instanceof ExprIdentNode) { ExprIdentNode identNodeInner = (ExprIdentNode) subNode; if (identNodeInner.getStreamId() == 0) { break; // for same event evals use the boolean expression, via count compare failing below } boolean isMustCoerce = false; Class numericCoercionType = JavaClassHelper.getBoxedType(lookupable.getReturnType()); if (identNodeInner.getExprEvaluator().getType() != lookupable.getReturnType()) { if (JavaClassHelper.isNumeric(lookupable.getReturnType())) { if (!JavaClassHelper.canCoerce(identNodeInner.getExprEvaluator().getType(), lookupable.getReturnType())) { throwConversionError(identNodeInner.getExprEvaluator().getType(), lookupable.getReturnType(), lookupable.getExpression()); } isMustCoerce = true; } else { break; // assumed not compatible } } FilterSpecParamInValue inValue; String streamName = identNodeInner.getResolvedStreamName(); if (arrayEventTypes != null && !arrayEventTypes.isEmpty() && arrayEventTypes.containsKey(streamName)) { Pair<Integer, String> indexAndProp = getStreamIndex(identNodeInner.getResolvedPropertyName()); inValue = new InSetOfValuesEventPropIndexed(identNodeInner.getResolvedStreamName(), indexAndProp.getFirst(), indexAndProp.getSecond(), isMustCoerce, numericCoercionType, statementName); } else { inValue = new InSetOfValuesEventProp(identNodeInner.getResolvedStreamName(), identNodeInner.getResolvedPropertyName(), isMustCoerce, numericCoercionType); } listofValues.add(inValue); } } // Fallback if not all values in the in-node can be resolved to properties or constants if (listofValues.size() == expectedNumberOfConstants) { return new FilterSpecParamIn(lookupable, op, listofValues); } return null; } private static FilterSpecParam handleEqualsAndRelOp(ExprNode constituent, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes, ExprEvaluatorContext exprEvaluatorContext, String statementName) throws ExprValidationException { FilterOperator op; if (constituent instanceof ExprEqualsNode) { ExprEqualsNode equalsNode = (ExprEqualsNode) constituent; if (!equalsNode.isIs()) { op = FilterOperator.EQUAL; if (equalsNode.isNotEquals()) { op = FilterOperator.NOT_EQUAL; } } else { op = FilterOperator.IS; if (equalsNode.isNotEquals()) { op = FilterOperator.IS_NOT; } } } else { ExprRelationalOpNode relNode = (ExprRelationalOpNode) constituent; if (relNode.getRelationalOpEnum() == RelationalOpEnum.GT) { op = FilterOperator.GREATER; } else if (relNode.getRelationalOpEnum() == RelationalOpEnum.LT) { op = FilterOperator.LESS; } else if (relNode.getRelationalOpEnum() == RelationalOpEnum.LE) { op = FilterOperator.LESS_OR_EQUAL; } else if (relNode.getRelationalOpEnum() == RelationalOpEnum.GE) { op = FilterOperator.GREATER_OR_EQUAL; } else { throw new IllegalStateException("Opertor '" + relNode.getRelationalOpEnum() + "' not mapped"); } } ExprNode left = constituent.getChildNodes().get(0); ExprNode right = constituent.getChildNodes().get(1); // check identifier and constant combination if ((ExprNodeUtility.isConstantValueExpr(right)) && (left instanceof ExprFilterOptimizableNode)) { ExprFilterOptimizableNode filterOptimizableNode = (ExprFilterOptimizableNode) left; if (filterOptimizableNode.getFilterLookupEligible()) { ExprConstantNode constantNode = (ExprConstantNode) right; FilterSpecLookupable lookupable = filterOptimizableNode.getFilterLookupable(); Object constant = constantNode.evaluate(null, true, exprEvaluatorContext); constant = handleConstantsCoercion(lookupable, constant); return new FilterSpecParamConstant(lookupable, op, constant); } } if ((ExprNodeUtility.isConstantValueExpr(left)) && (right instanceof ExprFilterOptimizableNode)) { ExprFilterOptimizableNode filterOptimizableNode = (ExprFilterOptimizableNode) right; if (filterOptimizableNode.getFilterLookupEligible()) { ExprConstantNode constantNode = (ExprConstantNode) left; FilterSpecLookupable lookupable = filterOptimizableNode.getFilterLookupable(); Object constant = constantNode.evaluate(null, true, exprEvaluatorContext); constant = handleConstantsCoercion(lookupable, constant); FilterOperator opReversed = op.isComparisonOperator() ? op.reversedRelationalOp() : op; return new FilterSpecParamConstant(lookupable, opReversed, constant); } } // check identifier and expression containing other streams if ((left instanceof ExprIdentNode) && (right instanceof ExprIdentNode)) { ExprIdentNode identNodeLeft = (ExprIdentNode) left; ExprIdentNode identNodeRight = (ExprIdentNode) right; if ((identNodeLeft.getStreamId() == 0) && (identNodeLeft.getFilterLookupEligible()) && (identNodeRight.getStreamId() != 0)) { return handleProperty(op, identNodeLeft, identNodeRight, arrayEventTypes, statementName); } if ((identNodeRight.getStreamId() == 0) && (identNodeRight.getFilterLookupEligible()) && (identNodeLeft.getStreamId() != 0)) { op = getReversedOperator(constituent, op); // reverse operators, as the expression is "stream1.prop xyz stream0.prop" return handleProperty(op, identNodeRight, identNodeLeft, arrayEventTypes, statementName); } } if ((left instanceof ExprFilterOptimizableNode) && (right instanceof ExprContextPropertyNode)) { ExprFilterOptimizableNode filterOptimizableNode = (ExprFilterOptimizableNode) left; ExprContextPropertyNode ctxNode = (ExprContextPropertyNode) right; FilterSpecLookupable lookupable = filterOptimizableNode.getFilterLookupable(); if (filterOptimizableNode.getFilterLookupEligible()) { SimpleNumberCoercer numberCoercer = getNumberCoercer(lookupable.getReturnType(), ctxNode.getType(), lookupable.getExpression()); return new FilterSpecParamContextProp(lookupable, op, ctxNode.getPropertyName(), ctxNode.getGetter(), numberCoercer); } } if ((left instanceof ExprContextPropertyNode) && (right instanceof ExprFilterOptimizableNode)) { ExprFilterOptimizableNode filterOptimizableNode = (ExprFilterOptimizableNode) right; ExprContextPropertyNode ctxNode = (ExprContextPropertyNode) left; FilterSpecLookupable lookupable = filterOptimizableNode.getFilterLookupable(); if (filterOptimizableNode.getFilterLookupEligible()) { op = getReversedOperator(constituent, op); // reverse operators, as the expression is "stream1.prop xyz stream0.prop" SimpleNumberCoercer numberCoercer = getNumberCoercer(lookupable.getReturnType(), ctxNode.getType(), lookupable.getExpression()); return new FilterSpecParamContextProp(lookupable, op, ctxNode.getPropertyName(), ctxNode.getGetter(), numberCoercer); } } return null; } private static FilterOperator getReversedOperator(ExprNode constituent, FilterOperator op) { if (!(constituent instanceof ExprRelationalOpNode)) { return op; } ExprRelationalOpNode relNode = (ExprRelationalOpNode) constituent; RelationalOpEnum relationalOpEnum = relNode.getRelationalOpEnum(); if (relationalOpEnum == RelationalOpEnum.GT) { return FilterOperator.LESS; } else if (relationalOpEnum == RelationalOpEnum.LT) { return FilterOperator.GREATER; } else if (relationalOpEnum == RelationalOpEnum.LE) { return FilterOperator.GREATER_OR_EQUAL; } else if (relationalOpEnum == RelationalOpEnum.GE) { return FilterOperator.LESS_OR_EQUAL; } return op; } private static FilterSpecParam handleProperty(FilterOperator op, ExprIdentNode identNodeLeft, ExprIdentNode identNodeRight, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes, String statementName) throws ExprValidationException { String propertyName = identNodeLeft.getResolvedPropertyName(); Class leftType = identNodeLeft.getExprEvaluator().getType(); Class rightType = identNodeRight.getExprEvaluator().getType(); SimpleNumberCoercer numberCoercer = getNumberCoercer(leftType, rightType, propertyName); boolean isMustCoerce = numberCoercer != null; Class numericCoercionType = JavaClassHelper.getBoxedType(leftType); String streamName = identNodeRight.getResolvedStreamName(); if (arrayEventTypes != null && !arrayEventTypes.isEmpty() && arrayEventTypes.containsKey(streamName)) { Pair<Integer, String> indexAndProp = getStreamIndex(identNodeRight.getResolvedPropertyName()); return new FilterSpecParamEventPropIndexed(identNodeLeft.getFilterLookupable(), op, identNodeRight.getResolvedStreamName(), indexAndProp.getFirst(), indexAndProp.getSecond(), isMustCoerce, numberCoercer, numericCoercionType, statementName); } return new FilterSpecParamEventProp(identNodeLeft.getFilterLookupable(), op, identNodeRight.getResolvedStreamName(), identNodeRight.getResolvedPropertyName(), isMustCoerce, numberCoercer, numericCoercionType, statementName); } private static SimpleNumberCoercer getNumberCoercer(Class leftType, Class rightType, String expression) throws ExprValidationException { Class numericCoercionType = JavaClassHelper.getBoxedType(leftType); if (rightType != leftType) { if (JavaClassHelper.isNumeric(rightType)) { if (!JavaClassHelper.canCoerce(rightType, leftType)) { throwConversionError(rightType, leftType, expression); } return SimpleNumberCoercerFactory.getCoercer(rightType, numericCoercionType); } } return null; } private static Pair<Integer, String> getStreamIndex(String resolvedPropertyName) { Property property = PropertyParser.parse(resolvedPropertyName, false); if (!(property instanceof NestedProperty)) { throw new IllegalStateException("Expected a nested property providing an index for array match '" + resolvedPropertyName + "'"); } NestedProperty nested = ((NestedProperty) property); if (nested.getProperties().size() < 2) { throw new IllegalStateException("Expected a nested property name for array match '" + resolvedPropertyName + "', none found"); } if (!(nested.getProperties().get(0) instanceof IndexedProperty)) { throw new IllegalStateException("Expected an indexed property for array match '" + resolvedPropertyName + "', please provide an index"); } int index = ((IndexedProperty) nested.getProperties().get(0)).getIndex(); nested.getProperties().remove(0); StringWriter writer = new StringWriter(); nested.toPropertyEPL(writer); return new Pair<Integer, String>(index, writer.toString()); } private static void throwConversionError(Class fromType, Class toType, String propertyName) throws ExprValidationException { String text = "Implicit conversion from datatype '" + fromType.getSimpleName() + "' to '" + toType.getSimpleName() + "' for property '" + propertyName + "' is not allowed (strict filter type coercion)"; throw new ExprValidationException(text); } // expressions automatically coerce to the most upwards type // filters require the same type private static Object handleConstantsCoercion(FilterSpecLookupable lookupable, Object constant) throws ExprValidationException { Class identNodeType = lookupable.getReturnType(); if (!JavaClassHelper.isNumeric(identNodeType)) { return constant; // no coercion required, other type checking performed by expression this comes from } if (constant == null) // null constant type { return null; } if (!JavaClassHelper.canCoerce(constant.getClass(), identNodeType)) { throwConversionError(constant.getClass(), identNodeType, lookupable.getExpression()); } Class identNodeTypeBoxed = JavaClassHelper.getBoxedType(identNodeType); return JavaClassHelper.coerceBoxed((Number) constant, identNodeTypeBoxed); } 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); } } } }