/* *************************************************************************************** * 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.core.start; import com.espertech.esper.client.EPStatementException; import com.espertech.esper.client.EventType; import com.espertech.esper.collection.UniformPair; import com.espertech.esper.core.service.ExprEvaluatorContextStatement; import com.espertech.esper.core.service.StatementContext; import com.espertech.esper.epl.core.EngineImportService; 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.baseagg.ExprAggregateNode; import com.espertech.esper.epl.expression.baseagg.ExprAggregateNodeUtil; import com.espertech.esper.epl.expression.core.*; import com.espertech.esper.epl.expression.ops.ExprEqualsNodeImpl; import com.espertech.esper.epl.expression.subquery.ExprSubselectNode; import com.espertech.esper.epl.expression.visitor.ExprNodeIdentifierCollectVisitor; import com.espertech.esper.epl.expression.visitor.ExprNodeSubselectDeclaredDotVisitor; import com.espertech.esper.epl.named.NamedWindowMgmtService; import com.espertech.esper.epl.spec.OnTriggerSetAssignment; import com.espertech.esper.epl.spec.OuterJoinDesc; import com.espertech.esper.epl.spec.OutputLimitRateType; import com.espertech.esper.epl.spec.StatementSpecCompiled; import com.espertech.esper.epl.view.OutputConditionExpressionFactory; import com.espertech.esper.util.JavaClassHelper; import com.espertech.esper.view.DataWindowViewFactory; import com.espertech.esper.view.ViewFactory; import com.espertech.esper.view.std.GroupByViewFactory; import com.espertech.esper.view.std.MergeViewFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; public class EPStatementStartMethodHelperValidate { private static final Logger log = LoggerFactory.getLogger(EPStatementStartMethodHelperValidate.class); public static void validateNoDataWindowOnNamedWindow(List<ViewFactory> viewFactories) throws ExprValidationException { for (ViewFactory viewFactory : viewFactories) { if ((viewFactory instanceof GroupByViewFactory) || ((viewFactory instanceof MergeViewFactory))) { continue; } if (viewFactory instanceof DataWindowViewFactory) { throw new ExprValidationException(NamedWindowMgmtService.ERROR_MSG_NO_DATAWINDOW_ALLOWED); } } } /** * Validate filter and join expression nodes. * * @param statementSpec the compiled statement * @param statementContext the statement services * @param typeService the event types for streams * @param viewResourceDelegate the delegate to verify expressions that use view resources */ protected static void validateNodes(StatementSpecCompiled statementSpec, StatementContext statementContext, StreamTypeService typeService, ViewResourceDelegateUnverified viewResourceDelegate) { EngineImportService engineImportService = statementContext.getEngineImportService(); ExprEvaluatorContextStatement evaluatorContextStmt = new ExprEvaluatorContextStatement(statementContext, false); String intoTableName = statementSpec.getIntoTableSpec() == null ? null : statementSpec.getIntoTableSpec().getName(); if (statementSpec.getFilterRootNode() != null) { ExprNode optionalFilterNode = statementSpec.getFilterRootNode(); // Validate where clause, initializing nodes to the stream ids used try { ExprValidationContext validationContext = new ExprValidationContext(typeService, engineImportService, statementContext.getStatementExtensionServicesContext(), viewResourceDelegate, statementContext.getSchedulingService(), statementContext.getVariableService(), statementContext.getTableService(), evaluatorContextStmt, statementContext.getEventAdapterService(), statementContext.getStatementName(), statementContext.getStatementId(), statementContext.getAnnotations(), statementContext.getContextDescriptor(), false, false, true, false, intoTableName, false); optionalFilterNode = ExprNodeUtility.getValidatedSubtree(ExprNodeOrigin.FILTER, optionalFilterNode, validationContext); if (optionalFilterNode.getExprEvaluator().getType() != boolean.class && optionalFilterNode.getExprEvaluator().getType() != Boolean.class) { throw new ExprValidationException("The where-clause filter expression must return a boolean value"); } statementSpec.setFilterExprRootNode(optionalFilterNode); // Make sure there is no aggregation in the where clause List<ExprAggregateNode> aggregateNodes = new LinkedList<ExprAggregateNode>(); ExprAggregateNodeUtil.getAggregatesBottomUp(optionalFilterNode, aggregateNodes); if (!aggregateNodes.isEmpty()) { throw new ExprValidationException("An aggregate function may not appear in a WHERE clause (use the HAVING clause)"); } } catch (ExprValidationException ex) { log.debug(".validateNodes Validation exception for filter=" + ExprNodeUtility.toExpressionStringMinPrecedenceSafe(optionalFilterNode), ex); throw new EPStatementException("Error validating expression: " + ex.getMessage(), ex, statementContext.getExpression()); } } if ((statementSpec.getOutputLimitSpec() != null) && ((statementSpec.getOutputLimitSpec().getWhenExpressionNode() != null) || (statementSpec.getOutputLimitSpec().getAndAfterTerminateExpr() != null))) { // Validate where clause, initializing nodes to the stream ids used try { EventType outputLimitType = OutputConditionExpressionFactory.getBuiltInEventType(statementContext.getEventAdapterService()); StreamTypeService typeServiceOutputWhen = new StreamTypeServiceImpl(new EventType[]{outputLimitType}, new String[]{null}, new boolean[]{true}, statementContext.getEngineURI(), false); ExprValidationContext validationContext = new ExprValidationContext(typeServiceOutputWhen, engineImportService, statementContext.getStatementExtensionServicesContext(), null, statementContext.getSchedulingService(), statementContext.getVariableService(), statementContext.getTableService(), evaluatorContextStmt, statementContext.getEventAdapterService(), statementContext.getStatementName(), statementContext.getStatementId(), statementContext.getAnnotations(), statementContext.getContextDescriptor(), false, false, false, false, intoTableName, false); ExprNode outputLimitWhenNode = statementSpec.getOutputLimitSpec().getWhenExpressionNode(); if (outputLimitWhenNode != null) { outputLimitWhenNode = ExprNodeUtility.getValidatedSubtree(ExprNodeOrigin.OUTPUTLIMIT, outputLimitWhenNode, validationContext); statementSpec.getOutputLimitSpec().setWhenExpressionNode(outputLimitWhenNode); if (JavaClassHelper.getBoxedType(outputLimitWhenNode.getExprEvaluator().getType()) != Boolean.class) { throw new ExprValidationException("The when-trigger expression in the OUTPUT WHEN clause must return a boolean-type value"); } EPStatementStartMethodHelperValidate.validateNoAggregations(outputLimitWhenNode, "An aggregate function may not appear in a OUTPUT LIMIT clause"); } // validate and-terminate expression if provided if (statementSpec.getOutputLimitSpec().getAndAfterTerminateExpr() != null) { if (statementSpec.getOutputLimitSpec().getRateType() != OutputLimitRateType.WHEN_EXPRESSION && statementSpec.getOutputLimitSpec().getRateType() != OutputLimitRateType.TERM) { throw new ExprValidationException("A terminated-and expression must be used with the OUTPUT WHEN clause"); } ExprNode validated = ExprNodeUtility.getValidatedSubtree(ExprNodeOrigin.OUTPUTLIMIT, statementSpec.getOutputLimitSpec().getAndAfterTerminateExpr(), validationContext); statementSpec.getOutputLimitSpec().setAndAfterTerminateExpr(validated); if (JavaClassHelper.getBoxedType(validated.getExprEvaluator().getType()) != Boolean.class) { throw new ExprValidationException("The terminated-and expression must return a boolean-type value"); } EPStatementStartMethodHelperValidate.validateNoAggregations(validated, "An aggregate function may not appear in a terminated-and clause"); } // validate then-expression validateThenSetAssignments(statementSpec.getOutputLimitSpec().getThenExpressions(), validationContext); // validate after-terminated then-expression validateThenSetAssignments(statementSpec.getOutputLimitSpec().getAndAfterTerminateThenExpressions(), validationContext); } catch (ExprValidationException ex) { throw new EPStatementException("Error validating expression: " + ex.getMessage(), statementContext.getExpression()); } } for (int outerJoinCount = 0; outerJoinCount < statementSpec.getOuterJoinDescList().length; outerJoinCount++) { OuterJoinDesc outerJoinDesc = statementSpec.getOuterJoinDescList()[outerJoinCount]; // validate on-expression nodes, if provided if (outerJoinDesc.getOptLeftNode() != null) { UniformPair<Integer> streamIdPair = validateOuterJoinPropertyPair(statementContext, outerJoinDesc.getOptLeftNode(), outerJoinDesc.getOptRightNode(), outerJoinCount, typeService, viewResourceDelegate); if (outerJoinDesc.getAdditionalLeftNodes() != null) { Set<Integer> streamSet = new HashSet<Integer>(); streamSet.add(streamIdPair.getFirst()); streamSet.add(streamIdPair.getSecond()); for (int i = 0; i < outerJoinDesc.getAdditionalLeftNodes().length; i++) { UniformPair<Integer> streamIdPairAdd = validateOuterJoinPropertyPair(statementContext, outerJoinDesc.getAdditionalLeftNodes()[i], outerJoinDesc.getAdditionalRightNodes()[i], outerJoinCount, typeService, viewResourceDelegate); // make sure all additional properties point to the same two streams if (!streamSet.contains(streamIdPairAdd.getFirst()) || (!streamSet.contains(streamIdPairAdd.getSecond()))) { String message = "Outer join ON-clause columns must refer to properties of the same joined streams" + " when using multiple columns in the on-clause"; throw new EPStatementException("Error validating expression: " + message, statementContext.getExpression()); } } } } } } private static void validateThenSetAssignments(List<OnTriggerSetAssignment> assignments, ExprValidationContext validationContext) throws ExprValidationException { if (assignments == null || assignments.isEmpty()) { return; } for (OnTriggerSetAssignment assign : assignments) { ExprNode node = ExprNodeUtility.getValidatedAssignment(assign, validationContext); assign.setExpression(node); EPStatementStartMethodHelperValidate.validateNoAggregations(node, "An aggregate function may not appear in a OUTPUT LIMIT clause"); } } protected static UniformPair<Integer> validateOuterJoinPropertyPair( StatementContext statementContext, ExprIdentNode leftNode, ExprIdentNode rightNode, int outerJoinCount, StreamTypeService typeService, ViewResourceDelegateUnverified viewResourceDelegate) { // Validate the outer join clause using an artificial equals-node on top. // Thus types are checked via equals. // Sets stream ids used for validated nodes. ExprNode equalsNode = new ExprEqualsNodeImpl(false, false); equalsNode.addChildNode(leftNode); equalsNode.addChildNode(rightNode); ExprEvaluatorContextStatement evaluatorContextStmt = new ExprEvaluatorContextStatement(statementContext, false); try { ExprValidationContext validationContext = new ExprValidationContext(typeService, statementContext.getEngineImportService(), statementContext.getStatementExtensionServicesContext(), viewResourceDelegate, statementContext.getSchedulingService(), statementContext.getVariableService(), statementContext.getTableService(), evaluatorContextStmt, statementContext.getEventAdapterService(), statementContext.getStatementName(), statementContext.getStatementId(), statementContext.getAnnotations(), statementContext.getContextDescriptor(), false, false, true, false, null, false); ExprNodeUtility.getValidatedSubtree(ExprNodeOrigin.JOINON, equalsNode, validationContext); } catch (ExprValidationException ex) { log.debug("Validation exception for outer join node=" + ExprNodeUtility.toExpressionStringMinPrecedenceSafe(equalsNode), ex); throw new EPStatementException("Error validating expression: " + ex.getMessage(), statementContext.getExpression()); } // Make sure we have left-hand-side and right-hand-side refering to different streams int streamIdLeft = leftNode.getStreamId(); int streamIdRight = rightNode.getStreamId(); if (streamIdLeft == streamIdRight) { String message = "Outer join ON-clause cannot refer to properties of the same stream"; throw new EPStatementException("Error validating expression: " + message, statementContext.getExpression()); } // Make sure one of the properties refers to the acutual stream currently being joined int expectedStreamJoined = outerJoinCount + 1; if ((streamIdLeft != expectedStreamJoined) && (streamIdRight != expectedStreamJoined)) { String message = "Outer join ON-clause must refer to at least one property of the joined stream" + " for stream " + expectedStreamJoined; throw new EPStatementException("Error validating expression: " + message, statementContext.getExpression()); } // Make sure neither of the streams refer to a 'future' stream String badPropertyName = null; if (streamIdLeft > outerJoinCount + 1) { badPropertyName = leftNode.getResolvedPropertyName(); } if (streamIdRight > outerJoinCount + 1) { badPropertyName = rightNode.getResolvedPropertyName(); } if (badPropertyName != null) { String message = "Outer join ON-clause invalid scope for property" + " '" + badPropertyName + "', expecting the current or a prior stream scope"; throw new EPStatementException("Error validating expression: " + message, statementContext.getExpression()); } return new UniformPair<Integer>(streamIdLeft, streamIdRight); } protected static ExprNode validateExprNoAgg(ExprNodeOrigin exprNodeOrigin, ExprNode exprNode, StreamTypeService streamTypeService, StatementContext statementContext, ExprEvaluatorContext exprEvaluatorContext, String errorMsg, boolean allowTableConsumption) throws ExprValidationException { ExprValidationContext validationContext = new ExprValidationContext(streamTypeService, statementContext.getEngineImportService(), statementContext.getStatementExtensionServicesContext(), null, statementContext.getSchedulingService(), statementContext.getVariableService(), statementContext.getTableService(), exprEvaluatorContext, statementContext.getEventAdapterService(), statementContext.getStatementName(), statementContext.getStatementId(), statementContext.getAnnotations(), statementContext.getContextDescriptor(), false, false, allowTableConsumption, false, null, false); ExprNode validated = ExprNodeUtility.getValidatedSubtree(exprNodeOrigin, exprNode, validationContext); validateNoAggregations(validated, errorMsg); return validated; } protected static void validateNoAggregations(ExprNode exprNode, String errorMsg) throws ExprValidationException { // Make sure there is no aggregation in the where clause List<ExprAggregateNode> aggregateNodes = new LinkedList<ExprAggregateNode>(); ExprAggregateNodeUtil.getAggregatesBottomUp(exprNode, aggregateNodes); if (!aggregateNodes.isEmpty()) { throw new ExprValidationException(errorMsg); } } // Special-case validation: When an on-merge query in the not-matched clause uses a subquery then // that subquery should not reference any of the stream's properties which are not-matched protected static void validateSubqueryExcludeOuterStream(ExprNode matchCondition) throws ExprValidationException { ExprNodeSubselectDeclaredDotVisitor visitorSubselects = new ExprNodeSubselectDeclaredDotVisitor(); matchCondition.accept(visitorSubselects); if (visitorSubselects.getSubselects().isEmpty()) { return; } ExprNodeIdentifierCollectVisitor visitorProps = new ExprNodeIdentifierCollectVisitor(); for (ExprSubselectNode node : visitorSubselects.getSubselects()) { if (node.getStatementSpecCompiled().getFilterRootNode() != null) { node.getStatementSpecCompiled().getFilterRootNode().accept(visitorProps); } } for (ExprIdentNode node : visitorProps.getExprProperties()) { if (node.getStreamId() == 1) { throw new ExprValidationException("On-Merge not-matched filter expression may not use properties that are provided by the named window event"); } } } }