/*
* *************************************************************************************
* 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.rowregex;
import com.espertech.esper.client.EventType;
import com.espertech.esper.client.annotation.HintEnum;
import com.espertech.esper.collection.Pair;
import com.espertech.esper.core.context.util.AgentInstanceContext;
import com.espertech.esper.core.context.util.AgentInstanceViewFactoryChainContext;
import com.espertech.esper.core.service.ExprEvaluatorContextStatement;
import com.espertech.esper.core.service.StatementContext;
import com.espertech.esper.epl.agg.service.AggregationServiceAggExpressionDesc;
import com.espertech.esper.epl.agg.service.AggregationServiceFactoryFactory;
import com.espertech.esper.epl.agg.service.AggregationServiceMatchRecognize;
import com.espertech.esper.epl.agg.service.AggregationServiceMatchRecognizeFactoryDesc;
import com.espertech.esper.epl.core.StreamTypeService;
import com.espertech.esper.epl.core.StreamTypeServiceImpl;
import com.espertech.esper.epl.expression.*;
import com.espertech.esper.epl.spec.MatchRecognizeDefineItem;
import com.espertech.esper.epl.spec.MatchRecognizeMeasureItem;
import com.espertech.esper.epl.spec.MatchRecognizeSpec;
import com.espertech.esper.util.CollectionUtil;
import com.espertech.esper.view.*;
import java.lang.annotation.Annotation;
import java.util.*;
/**
* View factory for match-recognize view.
*/
public class EventRowRegexNFAViewFactory extends ViewFactorySupport
{
private final MatchRecognizeSpec matchRecognizeSpec;
private final LinkedHashMap<String, Pair<Integer, Boolean>> variableStreams;
private final Map<Integer, String> streamVariables;
private final Set<String> variablesSingle;
private final EventType compositeEventType;
private final EventType rowEventType;
private final AggregationServiceMatchRecognize aggregationService;
private final List<AggregationServiceAggExpressionDesc> aggregationExpressions;
private final TreeMap<Integer, List<ExprPreviousMatchRecognizeNode>> callbacksPerIndex = new TreeMap<Integer, List<ExprPreviousMatchRecognizeNode>>();
private final boolean isUnbound;
private final boolean isIterateOnly;
private final boolean isSelectAsksMultimatches;
/**
* Ctor.
* @param viewChain views
* @param matchRecognizeSpec specification
* @param isUnbound true for unbound stream
* @param annotations annotations
* @throws ExprValidationException if validation fails
*/
public EventRowRegexNFAViewFactory(ViewFactoryChain viewChain, MatchRecognizeSpec matchRecognizeSpec, AgentInstanceContext agentInstanceContext, boolean isUnbound, Annotation[] annotations)
throws ExprValidationException
{
EventType parentViewType = viewChain.getEventType();
this.matchRecognizeSpec = matchRecognizeSpec;
this.isUnbound = isUnbound;
this.isIterateOnly = HintEnum.ITERATE_ONLY.getHint(annotations) != null;
StatementContext statementContext = agentInstanceContext.getStatementContext();
// Determine single-row and multiple-row variables
variablesSingle = new LinkedHashSet<String>();
Set<String> variablesMultiple = new LinkedHashSet<String>();
EventRowRegexHelper.recursiveInspectVariables(matchRecognizeSpec.getPattern(), false, variablesSingle, variablesMultiple);
// each variable gets associated with a stream number (multiple-row variables as well to hold the current event for the expression).
int streamNum = 0;
variableStreams = new LinkedHashMap<String, Pair<Integer, Boolean>>();
for (String variableSingle : variablesSingle)
{
variableStreams.put(variableSingle, new Pair<Integer, Boolean>(streamNum, false));
streamNum++;
}
for (String variableMultiple : variablesMultiple)
{
variableStreams.put(variableMultiple, new Pair<Integer, Boolean>(streamNum, true));
streamNum++;
}
// mapping of stream to variable
streamVariables = new TreeMap<Integer, String>();
for (Map.Entry<String, Pair<Integer, Boolean>> entry : variableStreams.entrySet())
{
streamVariables.put(entry.getValue().getFirst(), entry.getKey());
}
// assemble all single-row variables for expression validation
String[] singleVarStreamNames = new String[variableStreams.size()];
String[] allStreamNames = new String[variableStreams.size()];
EventType[] singleVarTypes = new EventType[variableStreams.size()];
EventType[] allTypes = new EventType[variableStreams.size()];
streamNum = 0;
for (String variableSingle : variablesSingle)
{
singleVarStreamNames[streamNum] = variableSingle;
singleVarTypes[streamNum] = parentViewType;
allStreamNames[streamNum] = variableSingle;
allTypes[streamNum] = parentViewType;
streamNum++;
}
for (String variableMultiple : variablesMultiple)
{
allStreamNames[streamNum] = variableMultiple;
allTypes[streamNum] = parentViewType;
streamNum++;
}
// determine type service for use with DEFINE
// validate each DEFINE clause expression
Set<String> definedVariables = new HashSet<String>();
List<ExprAggregateNode> aggregateNodes = new ArrayList<ExprAggregateNode>();
ExprEvaluatorContextStatement exprEvaluatorContext = new ExprEvaluatorContextStatement(statementContext);
for (MatchRecognizeDefineItem defineItem : matchRecognizeSpec.getDefines())
{
if (definedVariables.contains(defineItem.getIdentifier()))
{
throw new ExprValidationException("Variable '" + defineItem.getIdentifier() + "' has already been defined");
}
definedVariables.add(defineItem.getIdentifier());
String[] streamNamesDefine = new String[singleVarStreamNames.length];
System.arraycopy(singleVarStreamNames, 0, streamNamesDefine, 0, singleVarStreamNames.length);
EventType[] typesDefine = new EventType[singleVarTypes.length];
System.arraycopy(singleVarTypes, 0, typesDefine, 0, singleVarTypes.length);
boolean[] isIStreamOnly = new boolean[singleVarTypes.length];
Arrays.fill(isIStreamOnly, true);
// the own stream is available for querying
if (!variableStreams.containsKey(defineItem.getIdentifier()))
{
throw new ExprValidationException("Variable '" + defineItem.getIdentifier() + "' does not occur in pattern");
}
int streamNumDefine = variableStreams.get(defineItem.getIdentifier()).getFirst();
streamNamesDefine[streamNumDefine] = defineItem.getIdentifier();
typesDefine[streamNumDefine] = parentViewType;
StreamTypeService typeServiceDefines = new StreamTypeServiceImpl(typesDefine, streamNamesDefine, isIStreamOnly, statementContext.getEngineURI(), false);
ExprNode exprNodeResult = handlePreviousFunctions(defineItem.getExpression());
ExprValidationContext validationContext = new ExprValidationContext(typeServiceDefines, statementContext.getMethodResolutionService(), null, statementContext.getSchedulingService(), statementContext.getVariableService(), exprEvaluatorContext, statementContext.getEventAdapterService(), statementContext.getStatementName(), statementContext.getStatementId(), statementContext.getAnnotations(), statementContext.getContextDescriptor());
ExprNode validated = ExprNodeUtility.getValidatedSubtree(exprNodeResult, validationContext);
defineItem.setExpression(validated);
ExprAggregateNodeUtil.getAggregatesBottomUp(validated, aggregateNodes);
if (!aggregateNodes.isEmpty())
{
throw new ExprValidationException("An aggregate function may not appear in a DEFINE clause");
}
}
// determine type service for use with MEASURE
Map<String, Object> measureTypeDef = new LinkedHashMap<String, Object>();
for (String variableSingle : variablesSingle)
{
measureTypeDef.put(variableSingle, parentViewType);
}
for (String variableMultiple : variablesMultiple)
{
measureTypeDef.put(variableMultiple, new EventType[] {parentViewType});
}
String outputEventTypeName = statementContext.getStatementId() + "_rowrecog";
compositeEventType = statementContext.getEventAdapterService().createAnonymousMapType(outputEventTypeName, measureTypeDef);
StreamTypeService typeServiceMeasure = new StreamTypeServiceImpl(compositeEventType, "MATCH_RECOGNIZE", true, statementContext.getEngineURI());
// find MEASURE clause aggregations
boolean measureReferencesMultivar = false;
List<ExprAggregateNode> measureAggregateExprNodes = new ArrayList<ExprAggregateNode>();
for (MatchRecognizeMeasureItem measureItem : matchRecognizeSpec.getMeasures())
{
ExprAggregateNodeUtil.getAggregatesBottomUp(measureItem.getExpr(), measureAggregateExprNodes);
}
if (!measureAggregateExprNodes.isEmpty())
{
boolean[] isIStreamOnly = new boolean[allStreamNames.length];
Arrays.fill(isIStreamOnly, true);
StreamTypeServiceImpl typeServiceAggregateMeasure = new StreamTypeServiceImpl(allTypes, allStreamNames, isIStreamOnly, statementContext.getEngineURI(), false);
Map<Integer, List<ExprAggregateNode>> measureExprAggNodesPerStream = new HashMap<Integer, List<ExprAggregateNode>>();
for (ExprAggregateNode aggregateNode : measureAggregateExprNodes)
{
int count = 0;
ExprNodeIdentifierVisitor visitor = new ExprNodeIdentifierVisitor(true);
ExprValidationContext validationContext = new ExprValidationContext(typeServiceAggregateMeasure, statementContext.getMethodResolutionService(), null, statementContext.getSchedulingService(), statementContext.getVariableService(), exprEvaluatorContext, statementContext.getEventAdapterService(), statementContext.getStatementName(), statementContext.getStatementId(), statementContext.getAnnotations(), statementContext.getContextDescriptor());
for (ExprNode child : aggregateNode.getChildNodes())
{
ExprNode validated = ExprNodeUtility.getValidatedSubtree(child, validationContext);
validated.accept(visitor);
aggregateNode.getChildNodes().set(count++, new ExprNodeValidated(validated));
}
validationContext = new ExprValidationContext(typeServiceMeasure, statementContext.getMethodResolutionService(), null, statementContext.getSchedulingService(), statementContext.getVariableService(), exprEvaluatorContext, statementContext.getEventAdapterService(), statementContext.getStatementName(), statementContext.getStatementId(), statementContext.getAnnotations(), statementContext.getContextDescriptor());
aggregateNode.validate(validationContext);
// verify properties used within the aggregation
Set<Integer> aggregatedStreams = new HashSet<Integer>();
for (Pair<Integer, String> pair : visitor.getExprProperties())
{
aggregatedStreams.add(pair.getFirst());
}
Integer multipleVarStream = null;
for (int streamNumAggregated : aggregatedStreams)
{
String variable = streamVariables.get(streamNumAggregated);
if (variablesMultiple.contains(variable))
{
measureReferencesMultivar = true;
if (multipleVarStream == null)
{
multipleVarStream = streamNumAggregated;
continue;
}
throw new ExprValidationException("Aggregation functions in the measure-clause must only refer to properties of exactly one group variable returning multiple events");
}
}
if (multipleVarStream == null)
{
throw new ExprValidationException("Aggregation functions in the measure-clause must refer to one or more properties of exactly one group variable returning multiple events");
}
List<ExprAggregateNode> aggNodesForStream = measureExprAggNodesPerStream.get(multipleVarStream);
if (aggNodesForStream == null)
{
aggNodesForStream = new ArrayList<ExprAggregateNode>();
measureExprAggNodesPerStream.put(multipleVarStream, aggNodesForStream);
}
aggNodesForStream.add(aggregateNode);
}
AggregationServiceMatchRecognizeFactoryDesc factoryDesc = AggregationServiceFactoryFactory.getServiceMatchRecognize(streamVariables.size(), measureExprAggNodesPerStream, exprEvaluatorContext);
aggregationService = factoryDesc.getAggregationServiceFactory().makeService(agentInstanceContext);
aggregationExpressions = factoryDesc.getExpressions();
}
else
{
aggregationService = null;
aggregationExpressions = Collections.emptyList();
}
// validate each MEASURE clause expression
Map<String, Object> rowTypeDef = new LinkedHashMap<String, Object>();
ExprNodeIdentifierCollectVisitor streamRefVisitorNonAgg = new ExprNodeIdentifierCollectVisitor();
for (MatchRecognizeMeasureItem measureItem : matchRecognizeSpec.getMeasures())
{
if (measureItem.getName() == null)
{
throw new ExprValidationException("The measures clause requires that each expression utilizes the AS keyword to assign a column name");
}
ExprNode validated = validateMeasureClause(measureItem.getExpr(), typeServiceMeasure, variablesMultiple, variablesSingle, statementContext);
measureItem.setExpr(validated);
rowTypeDef.put(measureItem.getName(), validated.getExprEvaluator().getType());
validated.accept(streamRefVisitorNonAgg);
}
// Determine if any of the multi-var streams are referenced in the measures (non-aggregated only)
for (ExprIdentNode identNode : streamRefVisitorNonAgg.getExprProperties()) {
String rootPropName = identNode.getResolvedPropertyNameRoot();
if (variablesMultiple.contains(rootPropName) || (rootPropName == null)) {
measureReferencesMultivar = true;
break;
}
}
isSelectAsksMultimatches = measureReferencesMultivar;
// create rowevent type
String rowEventTypeName = statementContext.getStatementId() + "_rowrecogrow";
rowEventType = statementContext.getEventAdapterService().createAnonymousMapType(rowEventTypeName, rowTypeDef);
// validate partition-by expressions, if any
if (!matchRecognizeSpec.getPartitionByExpressions().isEmpty())
{
StreamTypeService typeServicePartition = new StreamTypeServiceImpl(parentViewType, "MATCH_RECOGNIZE_PARTITION", true, statementContext.getEngineURI());
List<ExprNode> validated = new ArrayList<ExprNode>();
ExprValidationContext validationContext = new ExprValidationContext(typeServicePartition, statementContext.getMethodResolutionService(), null, statementContext.getSchedulingService(), statementContext.getVariableService(), exprEvaluatorContext, statementContext.getEventAdapterService(), statementContext.getStatementName(), statementContext.getStatementId(), statementContext.getAnnotations(), statementContext.getContextDescriptor());
for (ExprNode partitionExpr : matchRecognizeSpec.getPartitionByExpressions())
{
validated.add(ExprNodeUtility.getValidatedSubtree(partitionExpr, validationContext));
}
matchRecognizeSpec.setPartitionByExpressions(validated);
}
// validate interval if present
if (matchRecognizeSpec.getInterval() != null) {
ExprValidationContext validationContext = new ExprValidationContext(new StreamTypeServiceImpl(statementContext.getEngineURI(), false), statementContext.getMethodResolutionService(), null, statementContext.getSchedulingService(), statementContext.getVariableService(), exprEvaluatorContext, statementContext.getEventAdapterService(), statementContext.getStatementName(), statementContext.getStatementId(), statementContext.getAnnotations(), statementContext.getContextDescriptor());
matchRecognizeSpec.getInterval().validate(validationContext);
}
}
private ExprNode validateMeasureClause(ExprNode measureNode, StreamTypeService typeServiceMeasure, Set<String> variablesMultiple, Set<String> variablesSingle, StatementContext statementContext)
throws ExprValidationException
{
try
{
ExprEvaluatorContextStatement exprEvaluatorContext = new ExprEvaluatorContextStatement(statementContext);
ExprValidationContext validationContext = new ExprValidationContext(typeServiceMeasure, statementContext.getMethodResolutionService(), null, statementContext.getSchedulingService(), statementContext.getVariableService(), exprEvaluatorContext, statementContext.getEventAdapterService(), statementContext.getStatementName(), statementContext.getStatementId(), statementContext.getAnnotations(), statementContext.getContextDescriptor());
return ExprNodeUtility.getValidatedSubtree(measureNode, validationContext);
}
catch (ExprValidationPropertyException e)
{
String grouped = CollectionUtil.toString(variablesMultiple);
String single = CollectionUtil.toString(variablesSingle);
String message = e.getMessage();
if (!variablesMultiple.isEmpty())
{
message += ", ensure that grouped variables (variables " + grouped + ") are accessed via index (i.e. variable[0].property) or appear within an aggregation";
}
if (!variablesSingle.isEmpty())
{
message += ", ensure that singleton variables (variables " + single + ") are not accessed via index";
}
throw new ExprValidationPropertyException(message, e);
}
}
private ExprNode handlePreviousFunctions(ExprNode defineItemExpression) throws ExprValidationException
{
ExprNodePreviousVisitorWParent previousVisitor = new ExprNodePreviousVisitorWParent();
defineItemExpression.accept(previousVisitor);
if (previousVisitor.getPrevious() == null)
{
return defineItemExpression;
}
for (Pair<ExprNode, ExprPreviousNode> previousNodePair : previousVisitor.getPrevious())
{
ExprPreviousNode previousNode = previousNodePair.getSecond();
ExprPreviousMatchRecognizeNode matchRecogPrevNode = new ExprPreviousMatchRecognizeNode();
if (previousNodePair.getSecond().getChildNodes().size() == 1)
{
matchRecogPrevNode.addChildNode(previousNode.getChildNodes().get(0));
matchRecogPrevNode.addChildNode(new ExprConstantNodeImpl(1));
}
else if (previousNodePair.getSecond().getChildNodes().size() == 2)
{
ExprNode first = previousNode.getChildNodes().get(0);
ExprNode second = previousNode.getChildNodes().get(1);
if ((first.isConstantResult()) && (!second.isConstantResult()))
{
matchRecogPrevNode.addChildNode(second);
matchRecogPrevNode.addChildNode(first);
}
else if ((!first.isConstantResult()) && (second.isConstantResult()))
{
matchRecogPrevNode.addChildNode(first);
matchRecogPrevNode.addChildNode(second);
}
else
{
throw new ExprValidationException("PREV operator requires a constant index");
}
}
if (previousNodePair.getFirst() == null)
{
defineItemExpression = matchRecogPrevNode;
}
else
{
ExprNodeUtility.replaceChildNode(previousNodePair.getFirst(), previousNodePair.getSecond(), matchRecogPrevNode);
}
// store in a list per index such that we can consolidate this into a single buffer
int index = matchRecogPrevNode.getConstantIndexNumber();
List<ExprPreviousMatchRecognizeNode> callbackList = callbacksPerIndex.get(index);
if (callbackList == null)
{
callbackList = new ArrayList<ExprPreviousMatchRecognizeNode>();
callbacksPerIndex.put(index, callbackList);
}
callbackList.add(matchRecogPrevNode);
}
return defineItemExpression;
}
public void setViewParameters(ViewFactoryContext viewFactoryContext, List<ExprNode> viewParameters) throws ViewParameterException {
}
public void attach(EventType parentEventType, StatementContext statementContext, ViewFactory optionalParentFactory, List<ViewFactory> parentViewFactories) throws ViewParameterException {
}
public View makeView(AgentInstanceViewFactoryChainContext agentInstanceViewFactoryContext) {
return new EventRowRegexNFAView(compositeEventType,
rowEventType,
matchRecognizeSpec,
variableStreams,
streamVariables,
variablesSingle,
agentInstanceViewFactoryContext.getAgentInstanceContext(),
callbacksPerIndex,
aggregationService,
isUnbound,
isIterateOnly,
isSelectAsksMultimatches
);
}
public EventType getEventType() {
return rowEventType;
}
public List<AggregationServiceAggExpressionDesc> getAggregationExpressions() {
return aggregationExpressions;
}
public AggregationServiceMatchRecognize getAggregationService() {
return aggregationService;
}
}