com.espertech.esper.epl.parse.EPLTreeWalker.java Source code

Java tutorial

Introduction

Here is the source code for com.espertech.esper.epl.parse.EPLTreeWalker.java

Source

/**************************************************************************************
 * 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.epl.parse;

import com.espertech.esper.antlr.ASTUtil;
import com.espertech.esper.client.ConfigurationInformation;
import com.espertech.esper.client.ConfigurationPlugInAggregationMultiFunction;
import com.espertech.esper.client.EPException;
import com.espertech.esper.collection.Pair;
import com.espertech.esper.collection.UniformPair;
import com.espertech.esper.core.context.mgr.ContextManagementService;
import com.espertech.esper.core.context.util.ContextDescriptor;
import com.espertech.esper.core.service.EPAdministratorHelper;
import com.espertech.esper.epl.agg.access.AggregationStateType;
import com.espertech.esper.epl.core.EngineImportException;
import com.espertech.esper.epl.core.EngineImportService;
import com.espertech.esper.epl.core.EngineImportSingleRowDesc;
import com.espertech.esper.epl.core.EngineImportUndefinedException;
import com.espertech.esper.epl.db.DatabasePollingViewableFactory;
import com.espertech.esper.epl.declexpr.ExprDeclaredHelper;
import com.espertech.esper.epl.declexpr.ExprDeclaredNodeImpl;
import com.espertech.esper.epl.declexpr.ExprDeclaredService;
import com.espertech.esper.epl.expression.*;
import com.espertech.esper.epl.generated.EsperEPL2Ast;
import com.espertech.esper.epl.script.ExprNodeScript;
import com.espertech.esper.epl.spec.*;
import com.espertech.esper.epl.variable.VariableService;
import com.espertech.esper.pattern.EvalFactoryNode;
import com.espertech.esper.pattern.PatternNodeFactory;
import com.espertech.esper.pattern.guard.GuardEnum;
import com.espertech.esper.plugin.PlugInAggregationMultiFunctionFactory;
import com.espertech.esper.rowregex.*;
import com.espertech.esper.schedule.SchedulingService;
import com.espertech.esper.schedule.TimeProvider;
import com.espertech.esper.type.*;
import com.espertech.esper.type.StringValue;
import com.espertech.esper.util.CollectionUtil;
import com.espertech.esper.util.LazyAllocatedMap;
import com.espertech.esper.util.PlaceholderParseException;
import com.espertech.esper.util.PlaceholderParser;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.tree.Tree;
import org.antlr.runtime.tree.TreeNodeStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.*;

/**
 * Called during the walks of a EPL expression AST tree as specified in the grammar file.
 * Constructs filter and view specifications etc.
 */
public class EPLTreeWalker extends EsperEPL2Ast {
    // private holding areas for accumulated info
    private Map<Tree, ExprNode> astExprNodeMap = new HashMap<Tree, ExprNode>();
    private final Stack<Map<Tree, ExprNode>> astExprNodeMapStack;

    private final Map<Tree, EvalFactoryNode> astPatternNodeMap = new HashMap<Tree, EvalFactoryNode>();

    private final Map<Tree, RowRegexExprNode> astRowRegexNodeMap = new HashMap<Tree, RowRegexExprNode>();

    private final Map<Tree, Object> astGOPNodeMap = new HashMap<Tree, Object>();

    private LazyAllocatedMap<ConfigurationPlugInAggregationMultiFunction, PlugInAggregationMultiFunctionFactory> plugInAggregations = new LazyAllocatedMap<ConfigurationPlugInAggregationMultiFunction, PlugInAggregationMultiFunctionFactory>();

    private FilterSpecRaw filterSpec;
    private final List<ViewSpec> viewSpecs = new LinkedList<ViewSpec>();

    // AST Walk result
    private List<ExprSubstitutionNode> substitutionParamNodes = new ArrayList<ExprSubstitutionNode>();
    private StatementSpecRaw statementSpec;
    private final Stack<StatementSpecRaw> statementSpecStack;

    private List<SelectClauseElementRaw> propertySelectRaw;
    private PropertyEvalSpec propertyEvalSpec;
    private List<OnTriggerMergeMatched> mergeMatcheds;
    private List<OnTriggerMergeAction> mergeActions;
    private Map<EvalFactoryNode, String> evalNodeExpressions;
    private ContextDescriptor contextDescriptor;

    private final EngineImportService engineImportService;
    private final VariableService variableService;
    private final TimeProvider timeProvider;
    private final ExprEvaluatorContext exprEvaluatorContext;
    private final SelectClauseStreamSelectorEnum defaultStreamSelector;
    private final String engineURI;
    private final ConfigurationInformation configurationInformation;
    private final SchedulingService schedulingService;
    private final PatternNodeFactory patternNodeFactory;
    private final ContextManagementService contextManagementService;
    private final CommonTokenStream tokenStream;
    private final List<String> scriptBodies;
    private final ExprDeclaredService exprDeclaredService;
    private final List<ExpressionScriptProvided> scriptExpressions;
    private final ExpressionDeclDesc expressionDeclarations;

    /**
     * Ctor.
     * @param engineImportService is required to resolve lib-calls into static methods or configured aggregation functions
     * @param variableService for variable access
     * @param input is the tree nodes to walk
     * @param defaultStreamSelector - the configuration for which insert or remove streams (or both) to produce
     * @param engineURI engine URI
     * @param configurationInformation configuration info
     */
    public EPLTreeWalker(TreeNodeStream input, CommonTokenStream tokenStream,
            EngineImportService engineImportService, VariableService variableService,
            SchedulingService schedulingService, SelectClauseStreamSelectorEnum defaultStreamSelector,
            String engineURI, ConfigurationInformation configurationInformation,
            PatternNodeFactory patternNodeFactory, ContextManagementService contextManagementService,
            List<String> scriptBodies, ExprDeclaredService exprDeclaredService) {
        super(input);
        this.tokenStream = tokenStream;
        this.engineImportService = engineImportService;
        this.variableService = variableService;
        this.defaultStreamSelector = defaultStreamSelector;
        this.timeProvider = schedulingService;
        this.patternNodeFactory = patternNodeFactory;
        this.exprEvaluatorContext = new ExprEvaluatorContextTimeOnly(timeProvider);
        this.engineURI = engineURI;
        this.configurationInformation = configurationInformation;
        this.schedulingService = schedulingService;
        this.contextManagementService = contextManagementService;
        this.scriptBodies = scriptBodies;
        this.exprDeclaredService = exprDeclaredService;

        if (defaultStreamSelector == null) {
            throw new IllegalArgumentException("Default stream selector is null");
        }

        statementSpec = new StatementSpecRaw(defaultStreamSelector);
        statementSpecStack = new Stack<StatementSpecRaw>();
        astExprNodeMapStack = new Stack<Map<Tree, ExprNode>>();

        // statement-global items
        expressionDeclarations = new ExpressionDeclDesc();
        statementSpec.setExpressionDeclDesc(expressionDeclarations);
        scriptExpressions = new ArrayList<ExpressionScriptProvided>(1);
        statementSpec.setScriptExpressions(scriptExpressions);
    }

    /**
     * Pushes a statement into the stack, creating a new empty statement to fill in.
     * The leave node method for lookup statements pops from the stack.
     * The leave node method for lookup statements pops from the stack.
     */
    protected void pushStmtContext() {
        if (log.isDebugEnabled()) {
            log.debug(".pushStmtContext");
        }
        statementSpecStack.push(statementSpec);
        astExprNodeMapStack.push(astExprNodeMap);

        statementSpec = new StatementSpecRaw(defaultStreamSelector);
        astExprNodeMap = new HashMap<Tree, ExprNode>();
    }

    /**
     * Returns statement specification.
     * @return statement spec.
     */
    public StatementSpecRaw getStatementSpec() {
        return statementSpec;
    }

    /**
     * Leave AST node and process it's type and child nodes.
     * @param node is the node to complete
     * @throws ASTWalkException if the node tree walk operation failed
     */
    protected void leaveNode(Tree node) throws ASTWalkException {
        if (log.isDebugEnabled()) {
            log.debug(".leaveNode " + node);
        }

        switch (node.getType()) {
        case STREAM_EXPR:
            leaveStreamExpr(node);
            break;
        case EVENT_FILTER_EXPR:
            leaveStreamFilter(node);
            break;
        case PATTERN_FILTER_EXPR:
            leavePatternFilter(node);
            break;
        case PATTERN_INCL_EXPR:
            return;
        case VIEW_EXPR:
            leaveView(node);
            break;
        case SELECTION_EXPR:
            leaveSelectClause(node);
            break;
        case WILDCARD_SELECT:
            leaveWildcardSelect();
            break;
        case SELECTION_ELEMENT_EXPR:
            leaveSelectionElement(node);
            break;
        case SELECTION_STREAM:
            leaveSelectionStream(node);
            break;
        case PROPERTY_SELECTION_ELEMENT_EXPR:
            leavePropertySelectionElement(node);
            break;
        case PROPERTY_SELECTION_STREAM:
            leavePropertySelectionStream(node);
            break;
        case PROPERTY_WILDCARD_SELECT:
            leavePropertyWildcardSelect();
            break;
        case EVENT_FILTER_PROPERTY_EXPR_ATOM:
            leavePropertySelectAtom(node);
            break;
        case EVENT_PROP_EXPR:
            leaveEventPropertyExpr(node);
            break;
        case EVAL_AND_EXPR:
            leaveJoinAndExpr(node);
            break;
        case EVAL_OR_EXPR:
            leaveJoinOrExpr(node);
            break;
        case EVAL_EQUALS_EXPR:
        case EVAL_NOTEQUALS_EXPR:
        case EVAL_IS_EXPR:
        case EVAL_ISNOT_EXPR:
            leaveEqualsExpr(node);
            break;
        case EVAL_EQUALS_GROUP_EXPR:
        case EVAL_NOTEQUALS_GROUP_EXPR:
        case EVAL_IS_GROUP_EXPR:
        case EVAL_ISNOT_GROUP_EXPR:
            leaveEqualsGroupExpr(node);
            break;
        case WHERE_EXPR:
            leaveWhereClause();
            break;
        case NUM_INT:
        case INT_TYPE:
        case LONG_TYPE:
        case BOOL_TYPE:
        case FLOAT_TYPE:
        case DOUBLE_TYPE:
        case STRING_TYPE:
        case NULL_TYPE:
            leaveConstant(node);
            break;
        case SUBSTITUTION:
            leaveSubstitution(node);
            break;
        case STAR:
        case MINUS:
        case PLUS:
        case DIV:
        case MOD:
            leaveMath(node);
            break;
        case BAND:
        case BOR:
        case BXOR:
            leaveBitWise(node);
            break;
        case LT:
        case GT:
        case LE:
        case GE:
            leaveRelationalOp(node);
            break;
        case COALESCE:
            leaveCoalesce(node);
            break;
        case NOT_EXPR:
            leaveExprNot(node);
            break;
        case PATTERN_NOT_EXPR:
            leavePatternNot(node);
            break;
        case SUM:
        case AVG:
        case COUNT:
        case MEDIAN:
        case STDDEV:
        case AVEDEV:
        case FIRST_AGGREG:
        case LAST_AGGREG:
        case WINDOW_AGGREG:
            leaveAggregate(node);
            break;
        case DOT_EXPR:
            leaveDotExpr(node);
            break;
        case LIB_FUNC_CHAIN:
            leaveLibFunctionChain(node);
            break;
        case LEFT_OUTERJOIN_EXPR:
        case RIGHT_OUTERJOIN_EXPR:
        case FULL_OUTERJOIN_EXPR:
        case INNERJOIN_EXPR:
            leaveOuterInnerJoin(node);
            break;
        case GROUP_BY_EXPR:
            leaveGroupBy(node);
            break;
        case HAVING_EXPR:
            leaveHavingClause();
            break;
        case ORDER_BY_EXPR:
            break;
        case ORDER_ELEMENT_EXPR:
            leaveOrderByElement(node);
            break;
        case EVENT_LIMIT_EXPR:
        case TIMEPERIOD_LIMIT_EXPR:
        case CRONTAB_LIMIT_EXPR:
        case WHEN_LIMIT_EXPR:
        case TERM_LIMIT_EXPR:
        case AFTER_LIMIT_EXPR:
            leaveOutputLimit(node);
            break;
        case ROW_LIMIT_EXPR:
            leaveRowLimit(node);
            break;
        case INSERTINTO_EXPR:
            leaveInsertInto(node);
            break;
        case CONCAT:
            leaveConcat(node);
            break;
        case CASE:
            leaveCaseNode(node, false);
            break;
        case CASE2:
            leaveCaseNode(node, true);
            break;
        case EVERY_EXPR:
            leaveEvery(node);
            break;
        case EVERY_DISTINCT_EXPR:
            leaveEveryDistinct(node);
            break;
        case FOLLOWED_BY_EXPR:
            leaveFollowedBy(node);
            break;
        case OR_EXPR:
            leaveOr(node);
            break;
        case AND_EXPR:
            leaveAnd(node);
            break;
        case GUARD_EXPR:
            leaveGuard(node);
            break;
        case OBSERVER_EXPR:
            leaveObserver(node);
            break;
        case MATCH_UNTIL_EXPR:
            leaveMatch(node);
            break;
        case IN_SET:
        case NOT_IN_SET:
            leaveInSet(node);
            break;
        case IN_RANGE:
        case NOT_IN_RANGE:
            leaveInRange(node);
            break;
        case BETWEEN:
        case NOT_BETWEEN:
            leaveBetween(node);
            break;
        case LIKE:
        case NOT_LIKE:
            leaveLike(node);
            break;
        case REGEXP:
        case NOT_REGEXP:
            leaveRegexp(node);
            break;
        case PREVIOUS:
        case PREVIOUSTAIL:
        case PREVIOUSWINDOW:
        case PREVIOUSCOUNT:
            leavePrevious(node);
            break;
        case PRIOR:
            leavePrior(node);
            break;
        case ARRAY_EXPR:
            leaveArray(node);
            break;
        case SUBSELECT_EXPR:
            leaveSubselectRow(node);
            break;
        case EXISTS_SUBSELECT_EXPR:
            leaveSubselectExists(node);
            break;
        case IN_SUBSELECT_EXPR:
        case NOT_IN_SUBSELECT_EXPR:
            leaveSubselectIn(node);
            break;
        case IN_SUBSELECT_QUERY_EXPR:
            leaveSubselectQueryIn(node);
            break;
        case INSTANCEOF:
            leaveInstanceOf(node);
            break;
        case TYPEOF:
            leaveTypeOf(node);
            break;
        case EXISTS:
            leaveExists(node);
            break;
        case ISTREAM:
            leaveIStreamBuiltin(node);
            break;
        case CAST:
            leaveCast(node);
            break;
        case CURRENT_TIMESTAMP:
            leaveTimestamp(node);
            break;
        case CREATE_WINDOW_EXPR:
            leaveCreateWindow(node);
            break;
        case CREATE_INDEX_EXPR:
            leaveCreateIndex(node);
            break;
        case CREATE_SCHEMA_EXPR:
            leaveCreateSchema(node);
            break;
        case CREATE_EXPR:
            leaveCreateExpression(node);
            break;
        case CREATE_WINDOW_SELECT_EXPR:
            leaveCreateWindowSelect();
            break;
        case CREATE_VARIABLE_EXPR:
            leaveCreateVariable(node);
            break;
        case ON_EXPR:
            leaveOnExpr(node);
            break;
        case UPDATE_EXPR:
            leaveUpdateExpr(node);
            break;
        case TIME_PERIOD:
            leaveTimePeriod(node);
            break;
        case NUMBERSETSTAR:
            leaveNumberSetStar(node);
            break;
        case NUMERIC_PARAM_FREQUENCY:
            leaveNumberSetFrequency(node);
            break;
        case NUMERIC_PARAM_RANGE:
            leaveNumberSetRange(node);
            break;
        case NUMERIC_PARAM_LIST:
            leaveNumberSetList(node);
            break;
        case LAST_OPERATOR:
        case LAST:
            leaveLastNumberSetOperator(node);
            break;
        case LW:
            leaveLastWeekdayNumberSetOperator(node);
            break;
        case WEEKDAY_OPERATOR:
            leaveWeekdayNumberSetOperator(node);
            break;
        case OBJECT_PARAM_ORDERED_EXPR:
            leaveObjectParamOrderedExpression(node);
            break;
        case ANNOTATION:
            leaveAnnotation(node);
            break;
        case MATCHREC_MEASURE_ITEM:
            leaveMatchRecognizeMeasureItem(node);
            break;
        case MATCHREC_PATTERN:
            leaveMatchRecognizePattern(node);
            break;
        case MATCHREC_PATTERN_NESTED:
            leaveMatchRecognizePatternNested(node);
            break;
        case MATCHREC_PATTERN_CONCAT:
            leaveMatchRecognizePatternConcat(node);
            break;
        case MATCHREC_PATTERN_ALTER:
            leaveMatchRecognizePatternAlter(node);
            break;
        case MATCHREC_PATTERN_ATOM:
            leaveMatchRecognizePatternAtom(node);
            break;
        case MATCHREC_DEFINE_ITEM:
            leaveMatchRecognizeDefineItem(node);
            break;
        case PARTITIONITEM:
            leaveMatchRecognizePartition(node);
            break;
        case MATCH_RECOGNIZE:
            leaveMatchRecognize(node);
            break;
        case ON_SELECT_EXPR:
            leaveOnSelect(node);
            break;
        case ON_STREAM:
            leaveOnStream(node);
            break;
        case FOR:
            leaveForClause(node);
            break;
        case MERGE_MAT:
        case MERGE_UNM:
            leaveMergeMatchedUnmatched(node);
            break;
        case MERGE_DEL:
            leaveMergeDelClause(node);
            break;
        case MERGE_UPD:
            leaveMergeUpdClause(node);
            break;
        case MERGE_INS:
            leaveMergeInsClause(node);
            break;
        case EXPRESSIONDECL:
            leaveExpressionDecl(node);
            break;
        case NEWKW:
            leaveNewKeyword(node);
            break;
        case CONTEXT:
            leaveContext(node);
            break;
        case CREATE_CTX:
            leaveCreateContext(node);
            break;
        case CREATE_DATAFLOW:
            leaveCreateDataflow(node);
            break;
        case GOPCFGITM:
        case GOPCFGEXP:
        case GOPCFGEPL:
            leaveGraphDetail(node);
            break;
        case JSON_ARRAY:
        case JSON_OBJECT:
            leaveJsonConstant(node);
            break;
        case DELETE:
            leaveFafDelete(node);
            break;
        case UPDATE:
            leaveFafUpdate(node);
            break;
        default:
            throw new ASTWalkException("Unhandled node type encountered, type '" + node.getType() + "' with text '"
                    + node.getText() + '\'');
        }

        // For each AST child node of this AST node that generated an ExprNode add the child node to the expression node.
        // This is for automatic expression tree building.
        if (!astExprNodeMap.isEmpty()) {
            mapChildASTToChildExprNode(node);
        }

        // For each AST child node of this AST node that generated an EvalNode add the EvalNode as a child
        if (!astPatternNodeMap.isEmpty()) {
            EvalFactoryNode thisPatternNode = astPatternNodeMap.get(node);
            for (int i = 0; i < node.getChildCount(); i++) {
                Tree childNode = node.getChild(i);
                EvalFactoryNode childEvalNode = astPatternNodeMap.get(childNode);
                if (childEvalNode != null) {
                    thisPatternNode.addChildNode(childEvalNode);
                    astPatternNodeMap.remove(childNode);
                }
            }
        }

        // For each AST child node of this AST node that generated an RowRegexExprNode add the RowRegexExprNode as a child
        if (!astRowRegexNodeMap.isEmpty()) {
            RowRegexExprNode thisRegexNode = astRowRegexNodeMap.get(node);
            for (int i = 0; i < node.getChildCount(); i++) {
                Tree childNode = node.getChild(i);
                RowRegexExprNode childEvalNode = astRowRegexNodeMap.get(childNode);
                if (childEvalNode != null) {
                    thisRegexNode.addChildNode(childEvalNode);
                    astRowRegexNodeMap.remove(childNode);
                }
            }
        }

        switch (node.getType()) {
        case SUM:
        case AVG:
        case COUNT:
        case MEDIAN:
        case STDDEV:
        case AVEDEV:
        case FIRST_AGGREG:
        case LAST_AGGREG:
        case WINDOW_AGGREG:
            postLeaveAggregate(node);
            break;
        default:
            break;
        }
    }

    private void mapChildASTToChildExprNode(Tree node) {
        ExprNode thisEvalNode = astExprNodeMap.get(node);
        for (int i = 0; i < node.getChildCount(); i++) {
            Tree childNode = node.getChild(i);

            ExprNode childEvalNode = astExprNodeMap.get(childNode);
            // If there was an expression node generated for the child node, and there is a current expression node,
            // add it to the current expression node (thisEvalNode)
            if ((childEvalNode != null) && (thisEvalNode != null)) {
                thisEvalNode.addChildNode(childEvalNode);
                astExprNodeMap.remove(childNode);
            }
        }
    }

    private void leaveCreateWindow(Tree node) {
        log.debug(".leaveCreateWindow");

        String windowName = node.getChild(0).getText();

        String eventName = null;
        Tree eventNameNode = ASTUtil.findFirstNode(node, CLASS_IDENT);
        if (eventNameNode != null) {
            eventName = eventNameNode.getText();
        }
        if (eventName == null) {
            eventName = "java.lang.Object";
        }

        boolean isRetainUnion = false;
        boolean isRetainIntersection = false;
        for (int i = 0; i < node.getChildCount(); i++) {
            if (node.getChild(i).getType() == RETAINUNION) {
                isRetainUnion = true;
                break;
            }
            if (node.getChild(i).getType() == RETAININTERSECTION) {
                isRetainIntersection = true;
                break;
            }
        }
        StreamSpecOptions streamSpecOptions = new StreamSpecOptions(false, isRetainUnion, isRetainIntersection);

        // handle table-create clause, i.e. (col1 type, col2 type)
        List<ColumnDesc> colums = ASTCreateSchemaHelper.getColTypeList(node);

        boolean isInsert = false;
        ExprNode insertWhereExpr = null;
        Tree insertNode = ASTUtil.findFirstNode(node, INSERT);
        if (insertNode != null) {
            isInsert = true;
            if (insertNode.getChildCount() > 0) {
                insertWhereExpr = ASTExprHelper.getRemoveExpr(insertNode.getChild(0), this.astExprNodeMap);
            }
        }

        CreateWindowDesc desc = new CreateWindowDesc(windowName, viewSpecs, streamSpecOptions, isInsert,
                insertWhereExpr, colums, eventName);
        statementSpec.setCreateWindowDesc(desc);

        // this is good for indicating what is being selected from
        FilterSpecRaw rawFilterSpec = new FilterSpecRaw(eventName, new LinkedList<ExprNode>(), null);
        FilterStreamSpecRaw streamSpec = new FilterStreamSpecRaw(rawFilterSpec, ViewSpec.EMPTY_VIEWSPEC_ARRAY, null,
                streamSpecOptions);
        statementSpec.getStreamSpecs().add(streamSpec);
    }

    private void leaveCreateIndex(Tree node) {
        log.debug(".leaveCreateIndex");

        String indexName = node.getChild(0).getText();
        String windowName = node.getChild(1).getText();

        Tree nodeExpr = node.getChild(2);
        List<CreateIndexItem> columns = new ArrayList<CreateIndexItem>();

        boolean unique = false;
        for (int i = 0; i < nodeExpr.getChildCount(); i++) {
            CreateIndexType type = CreateIndexType.HASH;
            Tree child = nodeExpr.getChild(i);
            if (child.getType() == INDEXCOL) {
                String columnName = child.getChild(0).getText();
                if (child.getChildCount() == 2) {
                    String typeName = child.getChild(1).getText();
                    try {
                        type = CreateIndexType.valueOf(typeName.toUpperCase());
                    } catch (RuntimeException ex) {
                        throw new ASTWalkException("Invalid column index type '" + typeName
                                + "' encountered, please use any of the following index type names "
                                + Arrays.asList(CreateIndexType.values()));
                    }
                }
                columns.add(new CreateIndexItem(columnName, type));
            }
        }

        int last = node.getChildCount() - 1;
        if (node.getChild(last).getType() == IDENT) {
            String ident = node.getChild(last).getText();
            if (ident.toLowerCase().trim().equals("unique")) {
                unique = true;
            } else {
                throw new ASTWalkException(
                        "Invalid keyword '" + ident + "' in create-index encountered, expected 'unique'");
            }
        }

        statementSpec.setCreateIndexDesc(new CreateIndexDesc(unique, indexName, windowName, columns));
    }

    private void leaveCreateSchema(Tree node) {
        log.debug(".leaveCreateSchema");
        CreateSchemaDesc createSchema = ASTCreateSchemaHelper.walkCreateSchema(node);
        statementSpec.getStreamSpecs()
                .add(new FilterStreamSpecRaw(
                        new FilterSpecRaw(Object.class.getName(), Collections.<ExprNode>emptyList(), null),
                        ViewSpec.EMPTY_VIEWSPEC_ARRAY, null, new StreamSpecOptions()));
        statementSpec.setCreateSchemaDesc(createSchema);
    }

    private void leaveCreateExpression(Tree node) {
        log.debug(".leaveCreateExpression");
        Pair<ExpressionDeclItem, ExpressionScriptProvided> pair = ASTExpressionDeclHelper
                .walkExpressionDecl(node.getChild(0), scriptBodies, astExprNodeMap);
        statementSpec.setCreateExpressionDesc(new CreateExpressionDesc(pair));
    }

    private void leaveCreateVariable(Tree node) {
        log.debug(".leaveCreateVariable");

        Tree child = node.getChild(0);
        String variableType = child.getText();
        child = node.getChild(1);
        String variableName = child.getText();

        int start = 2;
        boolean constant = false;
        boolean array = false;
        if (node.getChildCount() > start && node.getChild(start).getType() == IDENT) {
            String text = node.getChild(start).getText().toLowerCase().trim();
            if (text.equals("constant") || text.equals("const")) {
                constant = true;
            } else {
                throw new EPException(
                        "Expected 'constant' or 'const' keyword after create for create-variable syntax but encountered '"
                                + text + "'");
            }
            start++;
        }
        if (node.getChildCount() > start && node.getChild(start).getType() == LBRACK) {
            array = true;
            start++;
        }

        ExprNode assignment = null;
        if (node.getChildCount() > start) {
            child = node.getChild(start);
            assignment = astExprNodeMap.remove(child);
        }

        CreateVariableDesc desc = new CreateVariableDesc(variableType, variableName, assignment, constant, array);
        statementSpec.setCreateVariableDesc(desc);
    }

    private void leaveCreateWindowSelect() {
        log.debug(".leaveCreateWindowSelect");
    }

    private void leaveOnExpr(Tree node) {
        log.debug(".leaveOnExpr");

        // determine on-delete or on-select
        boolean isOnDelete = false;
        Tree typeChildNode = null;

        for (int i = 0; i < node.getChildCount(); i++) {
            Tree childNode = node.getChild(i);

            if (childNode.getType() == ON_DELETE_EXPR) {
                typeChildNode = childNode;
                isOnDelete = true;
            } else if (childNode.getType() == ON_SELECT_EXPR) {
                typeChildNode = childNode;
            } else if (childNode.getType() == ON_UPDATE_EXPR) {
                typeChildNode = childNode;
            } else if (childNode.getType() == ON_SET_EXPR) {
                typeChildNode = childNode;
            } else if (childNode.getType() == ON_MERGE_EXPR) {
                typeChildNode = childNode;
            }
        }
        if (typeChildNode == null) {
            throw new IllegalStateException("Could not determine on-expr type");
        }

        if (typeChildNode.getType() == ON_MERGE_EXPR) {
            String windowName = typeChildNode.getChild(0).getText();
            String asName = null;
            if (typeChildNode.getChild(1).getType() == IDENT) {
                asName = typeChildNode.getChild(1).getText();
            }

            OnTriggerMergeDesc desc = new OnTriggerMergeDesc(windowName, asName,
                    mergeMatcheds == null ? Collections.<OnTriggerMergeMatched>emptyList() : mergeMatcheds);
            statementSpec.setOnTriggerDesc(desc);
        } else if (typeChildNode.getType() != ON_SET_EXPR) {
            // The ON_EXPR_FROM contains the window name
            UniformPair<String> windowName = getWindowName(typeChildNode);
            boolean deleteAndSelect = typeChildNode.getChild(0).getType() == DELETE;
            if (windowName == null) {
                // on the statement spec, the deepest spec is the outermost
                List<OnTriggerSplitStream> splitStreams = new ArrayList<OnTriggerSplitStream>();
                for (int i = 1; i <= statementSpecStack.size() - 1; i++) {
                    StatementSpecRaw raw = statementSpecStack.get(i);
                    splitStreams.add(new OnTriggerSplitStream(raw.getInsertIntoDesc(), raw.getSelectClauseSpec(),
                            raw.getFilterExprRootNode()));
                }
                splitStreams.add(new OnTriggerSplitStream(statementSpec.getInsertIntoDesc(),
                        statementSpec.getSelectClauseSpec(), statementSpec.getFilterExprRootNode()));
                if (!statementSpecStack.isEmpty()) {
                    statementSpec = statementSpecStack.get(0);
                }
                boolean isFirst = isSelectInsertFirst(node);
                statementSpec.setOnTriggerDesc(
                        new OnTriggerSplitStreamDesc(OnTriggerType.ON_SPLITSTREAM, isFirst, splitStreams));
                statementSpecStack.clear();
            } else if (typeChildNode.getType() == ON_UPDATE_EXPR) {
                List<OnTriggerSetAssignment> assignments = ASTExprHelper.getOnTriggerSetAssignments(typeChildNode,
                        astExprNodeMap);
                statementSpec.setOnTriggerDesc(
                        new OnTriggerWindowUpdateDesc(windowName.getFirst(), windowName.getSecond(), assignments));
                statementSpec.setFilterExprRootNode(getRemoveFirstByType(typeChildNode, WHERE_EXPR));
            } else {
                statementSpec
                        .setOnTriggerDesc(new OnTriggerWindowDesc(windowName.getFirst(), windowName.getSecond(),
                                isOnDelete ? OnTriggerType.ON_DELETE : OnTriggerType.ON_SELECT, deleteAndSelect));
            }
        } else {
            List<OnTriggerSetAssignment> assignments = ASTExprHelper.getOnTriggerSetAssignments(typeChildNode,
                    astExprNodeMap);
            statementSpec.setOnTriggerDesc(new OnTriggerSetDesc(assignments));
        }
    }

    private void leaveOnStream(Tree node) {
        log.debug(".leaveOnStream");

        // get optional filter stream as-name
        Tree childNode = node.getChild(1);
        String streamAsName = null;
        if ((childNode != null) && (childNode.getType() == IDENT)) {
            streamAsName = childNode.getText();
        }

        // get stream to use (pattern or filter)
        StreamSpecRaw streamSpec;
        if (node.getChild(0).getType() == EVENT_FILTER_EXPR) {
            streamSpec = new FilterStreamSpecRaw(filterSpec, ViewSpec.EMPTY_VIEWSPEC_ARRAY, streamAsName,
                    new StreamSpecOptions());
        } else if (node.getChild(0).getType() == PATTERN_INCL_EXPR) {
            if ((astPatternNodeMap.size() > 1) || ((astPatternNodeMap.isEmpty()))) {
                throw new ASTWalkException(
                        "Unexpected AST tree contains zero or more then 1 child elements for root");
            }
            // Get expression node sub-tree from the AST nodes placed so far
            EvalFactoryNode evalNode = astPatternNodeMap.values().iterator().next();
            streamSpec = new PatternStreamSpecRaw(evalNode, evalNodeExpressions, ViewSpec.toArray(viewSpecs),
                    streamAsName, new StreamSpecOptions());
            if (evalNodeExpressions != null) {
                evalNodeExpressions = new HashMap<EvalFactoryNode, String>();
            }
            astPatternNodeMap.clear();
        } else {
            throw new IllegalStateException("Invalid AST type node, cannot map to stream specification");
        }

        statementSpec.getStreamSpecs().add(streamSpec);
    }

    private void leaveForClause(Tree node) {
        log.debug(".leaveForClause");

        if (statementSpec.getForClauseSpec() == null) {
            statementSpec.setForClauseSpec(new ForClauseSpec());
        }
        String ident = node.getChild(0).getText();
        List<ExprNode> expressions = ASTExprHelper.getExprNodes(node, 1, astExprNodeMap);
        statementSpec.getForClauseSpec().getClauses().add(new ForClauseItemSpec(ident, expressions));
    }

    private void leaveMergeMatchedUnmatched(Tree node) {
        log.debug(".leaveMergeMatchedUnmatched");

        boolean matched = node.getType() == MERGE_MAT;
        if (mergeMatcheds == null) {
            mergeMatcheds = new ArrayList<OnTriggerMergeMatched>();
        }
        ExprNode filterSpec = null;
        if (node.getChildCount() > 0) {
            filterSpec = ASTExprHelper.getRemoveExpr(node.getChild(node.getChildCount() - 1), astExprNodeMap);
        }
        mergeMatcheds.add(new OnTriggerMergeMatched(matched, filterSpec, mergeActions));
        mergeActions = null;
    }

    private void leaveMergeDelClause(Tree node) {
        log.debug(".leaveMergeDelClause");

        if (mergeActions == null) {
            mergeActions = new ArrayList<OnTriggerMergeAction>();
        }
        Tree whereCondNode = ASTUtil.findFirstNode(node, WHERE_EXPR);
        ExprNode whereCond = whereCondNode != null
                ? ASTExprHelper.getRemoveExpr(whereCondNode.getChild(0), astExprNodeMap)
                : null;
        mergeActions.add(new OnTriggerMergeActionDelete(whereCond));
    }

    private void leaveMergeUpdClause(Tree node) {
        log.debug(".leaveMergeUpdClause");

        if (mergeActions == null) {
            mergeActions = new ArrayList<OnTriggerMergeAction>();
        }
        Tree whereCondNode = ASTUtil.findFirstNode(node, WHERE_EXPR);
        ExprNode whereCond = whereCondNode != null
                ? ASTExprHelper.getRemoveExpr(whereCondNode.getChild(0), astExprNodeMap)
                : null;

        List<OnTriggerSetAssignment> sets = ASTExprHelper.getOnTriggerSetAssignments(node, astExprNodeMap);
        mergeActions.add(new OnTriggerMergeActionUpdate(whereCond, sets));
    }

    private void leaveMergeInsClause(Tree node) {
        log.debug(".leaveMergeInsClause");

        Tree whereCondNode = ASTUtil.findFirstNode(node, WHERE_EXPR);
        ExprNode whereCond = whereCondNode != null
                ? ASTExprHelper.getRemoveExpr(whereCondNode.getChild(0), astExprNodeMap)
                : null;

        List<SelectClauseElementRaw> expressions = new ArrayList<SelectClauseElementRaw>(
                statementSpec.getSelectClauseSpec().getSelectExprList());
        statementSpec.getSelectClauseSpec().getSelectExprList().clear();

        Tree optInsertNameNode = ASTUtil.findFirstNode(node, CLASS_IDENT);
        String optionalInsertName = optInsertNameNode != null ? optInsertNameNode.getText() : null;

        List<String> columsList = Collections.emptyList();
        for (int i = 0; i < node.getChildCount(); i++) {
            if (node.getChild(i).getType() == EXPRCOL) {
                columsList = ASTLibHelper.getIdentList(node.getChild(i));
            }
        }

        if (mergeActions == null) {
            mergeActions = new ArrayList<OnTriggerMergeAction>();
        }
        mergeActions.add(new OnTriggerMergeActionInsert(whereCond, optionalInsertName, columsList, expressions));
    }

    private void leaveUpdateExpr(Tree node) {
        log.debug(".leaveUpdateExpr");

        Tree parentUpdateNode = node.getChild(0);
        String eventTypeName = parentUpdateNode.getChild(0).getText();
        FilterStreamSpecRaw streamSpec = new FilterStreamSpecRaw(
                new FilterSpecRaw(eventTypeName, Collections.<ExprNode>emptyList(), null),
                ViewSpec.EMPTY_VIEWSPEC_ARRAY, eventTypeName, new StreamSpecOptions());
        statementSpec.getStreamSpecs().add(streamSpec);

        String optionalStreamName = null;
        if ((parentUpdateNode.getChildCount() > 1) && (parentUpdateNode.getChild(1).getType() == IDENT)) {
            optionalStreamName = parentUpdateNode.getChild(1).getText();
        }

        List<OnTriggerSetAssignment> assignments = ASTExprHelper.getOnTriggerSetAssignments(parentUpdateNode,
                astExprNodeMap);
        ExprNode whereClause = this.getRemoveFirstByType(parentUpdateNode, WHERE_EXPR);
        statementSpec.setUpdateDesc(new UpdateDesc(optionalStreamName, assignments, whereClause));
    }

    private UniformPair<String> getWindowName(Tree typeChildNode) {
        String windowName = null;
        String windowStreamName = null;

        for (int i = 0; i < typeChildNode.getChildCount(); i++) {
            Tree child = typeChildNode.getChild(i);
            if (child.getType() == ON_EXPR_FROM) {
                windowName = child.getChild(0).getText();
                if (child.getChildCount() > 1) {
                    windowStreamName = child.getChild(1).getText();
                }
                break;
            }
        }
        if (windowName == null) {
            return null;
        }
        return new UniformPair<String>(windowName, windowStreamName);
    }

    private void leavePrevious(Tree node) {
        log.debug(".leavePrevious");

        PreviousType previousType;
        if (node.getType() == PREVIOUS) {
            previousType = PreviousType.PREV;
        } else if (node.getType() == PREVIOUSTAIL) {
            previousType = PreviousType.PREVTAIL;
        } else if (node.getType() == PREVIOUSWINDOW) {
            previousType = PreviousType.PREVWINDOW;
        } else if (node.getType() == PREVIOUSCOUNT) {
            previousType = PreviousType.PREVCOUNT;
        } else {
            throw new IllegalStateException("Failed to handle type '" + node.getType() + "'");
        }

        ExprPreviousNode previousNode = new ExprPreviousNode(previousType);
        astExprNodeMap.put(node, previousNode);
    }

    private void leavePrior(Tree node) {
        log.debug(".leavePrior");

        ExprPriorNode priorNode = new ExprPriorNode();
        astExprNodeMap.put(node, priorNode);
    }

    private void leaveInstanceOf(Tree node) {
        log.debug(".leaveInstanceOf");

        // get class identifiers
        List<String> classes = new ArrayList<String>();
        for (int i = 1; i < node.getChildCount(); i++) {
            Tree classIdent = node.getChild(i);
            classes.add(classIdent.getText());
        }

        String idents[] = classes.toArray(new String[classes.size()]);
        ExprInstanceofNode instanceofNode = new ExprInstanceofNode(idents);
        astExprNodeMap.put(node, instanceofNode);
    }

    private void leaveTypeOf(Tree node) {
        log.debug(".leaveTypeOf");
        ExprTypeofNode typeofNode = new ExprTypeofNode();
        astExprNodeMap.put(node, typeofNode);
    }

    private void leaveExists(Tree node) {
        log.debug(".leaveExists");

        ExprPropertyExistsNode instanceofNode = new ExprPropertyExistsNode();
        astExprNodeMap.put(node, instanceofNode);
    }

    private void leaveIStreamBuiltin(Tree node) {
        log.debug(".leaveIStreamBuiltin");

        ExprIStreamNode istreamNode = new ExprIStreamNode();
        astExprNodeMap.put(node, istreamNode);
    }

    private void leaveCast(Tree node) {
        log.debug(".leaveCast");

        String classIdent = node.getChild(1).getText();
        ExprCastNode castNode = new ExprCastNode(classIdent);
        astExprNodeMap.put(node, castNode);
    }

    private void leaveTimestamp(Tree node) {
        log.debug(".leaveTimestamp");

        ExprTimestampNode timeNode = new ExprTimestampNode();
        astExprNodeMap.put(node, timeNode);
    }

    private void leaveTimePeriod(Tree node) {
        log.debug(".leaveTimePeriod");

        ExprTimePeriod timeNode = ASTExprHelper.getTimePeriodExpr(node, astExprNodeMap);
        astExprNodeMap.put(node, timeNode);
    }

    private void leaveNumberSetStar(Tree node) {
        log.debug(".leaveNumberSetStar");
        ExprNumberSetWildcard exprNode = new ExprNumberSetWildcard();
        astExprNodeMap.put(node, exprNode);
    }

    private void leaveNumberSetFrequency(Tree node) {
        log.debug(".leaveNumberSetFrequency");
        ExprNumberSetFrequency exprNode = new ExprNumberSetFrequency();
        astExprNodeMap.put(node, exprNode);
    }

    private void leaveNumberSetRange(Tree node) {
        log.debug(".leaveNumberSetRange");
        ExprNumberSetRange exprNode = new ExprNumberSetRange();
        astExprNodeMap.put(node, exprNode);
    }

    private void leaveNumberSetList(Tree node) {
        log.debug(".leaveNumberSetList");
        ExprNumberSetList exprNode = new ExprNumberSetList();
        astExprNodeMap.put(node, exprNode);
    }

    private void leaveLastNumberSetOperator(Tree node) {
        log.debug(".leaveLastNumberSetOperator");
        ExprNumberSetCronParam exprNode = new ExprNumberSetCronParam(CronOperatorEnum.LASTDAY);
        astExprNodeMap.put(node, exprNode);
    }

    private void leaveLastWeekdayNumberSetOperator(Tree node) {
        log.debug(".leaveLastWeekdayNumberSetOperator");
        ExprNumberSetCronParam exprNode = new ExprNumberSetCronParam(CronOperatorEnum.LASTWEEKDAY);
        astExprNodeMap.put(node, exprNode);
    }

    private void leaveWeekdayNumberSetOperator(Tree node) {
        log.debug(".leaveWeekdayNumberSetOperator");
        ExprNumberSetCronParam exprNode = new ExprNumberSetCronParam(CronOperatorEnum.WEEKDAY);
        astExprNodeMap.put(node, exprNode);
    }

    private void leaveObjectParamOrderedExpression(Tree node) {
        log.debug(".leaveObjectParamOrderedExpression");

        boolean isDescending = false;
        if ((node.getChildCount() > 1) && (node.getChild(1).getText().toUpperCase().equals("DESC"))) {
            isDescending = true;
        }
        ExprOrderedExpr exprNode = new ExprOrderedExpr(isDescending);
        astExprNodeMap.put(node, exprNode);
    }

    private void leaveAnnotation(Tree node) {
        log.debug(".leaveAnnotation");
        statementSpec.getAnnotations().add(ASTAnnotationHelper.walk(node, this.engineImportService));
    }

    private void leaveArray(Tree node) {
        log.debug(".leaveArray");

        ExprArrayNode arrayNode = new ExprArrayNode();
        astExprNodeMap.put(node, arrayNode);
    }

    private void leaveSubselectRow(Tree node) {
        log.debug(".leaveSubselectRow");

        StatementSpecRaw currentSpec = popStacks();
        ExprSubselectRowNode subselectNode = new ExprSubselectRowNode(currentSpec);
        astExprNodeMap.put(node, subselectNode);
    }

    private void leaveSubselectExists(Tree node) {
        log.debug(".leaveSubselectExists");

        StatementSpecRaw currentSpec = popStacks();
        ExprSubselectNode subselectNode = new ExprSubselectExistsNode(currentSpec);
        astExprNodeMap.put(node, subselectNode);
    }

    private void leaveSubselectIn(Tree node) {
        log.debug(".leaveSubselectIn");

        Tree nodeSubquery = node.getChild(1);

        boolean isNot = false;
        if (node.getType() == NOT_IN_SUBSELECT_EXPR) {
            isNot = true;
        }

        ExprSubselectInNode subqueryNode = (ExprSubselectInNode) astExprNodeMap.remove(nodeSubquery);
        subqueryNode.setNotIn(isNot);

        astExprNodeMap.put(node, subqueryNode);
    }

    private void leaveSubselectQueryIn(Tree node) {
        log.debug(".leaveSubselectQueryIn");

        StatementSpecRaw currentSpec = popStacks();
        ExprSubselectNode subselectNode = new ExprSubselectInNode(currentSpec);
        astExprNodeMap.put(node, subselectNode);
    }

    private StatementSpecRaw popStacks() {
        log.debug(".popStacks");

        StatementSpecRaw currentSpec = statementSpec;
        statementSpec = statementSpecStack.pop();

        if (currentSpec.isHasVariables()) {
            statementSpec.setHasVariables(true);
        }
        if (currentSpec.getReferencedVariables() != null) {
            for (String var : currentSpec.getReferencedVariables()) {
                addVariable(statementSpec, var);
            }
        }

        astExprNodeMap = astExprNodeMapStack.pop();

        return currentSpec;
    }

    /**
     * End processing of the AST tree for stand-alone pattern expressions.
     * @throws ASTWalkException is the walk failed
     */
    protected void endPattern() throws ASTWalkException {
        log.debug(".endPattern");

        if ((astPatternNodeMap.size() > 1) || ((astPatternNodeMap.isEmpty()))) {
            throw new ASTWalkException("Unexpected AST tree contains zero or more then 1 child elements for root");
        }

        // Get expression node sub-tree from the AST nodes placed so far
        EvalFactoryNode evalNode = astPatternNodeMap.values().iterator().next();

        PatternStreamSpecRaw streamSpec = new PatternStreamSpecRaw(evalNode, evalNodeExpressions,
                ViewSpec.EMPTY_VIEWSPEC_ARRAY, null, new StreamSpecOptions());
        if (evalNodeExpressions != null) {
            evalNodeExpressions = new HashMap<EvalFactoryNode, String>();
        }
        statementSpec.getStreamSpecs().add(streamSpec);
        statementSpec.setSubstitutionParameters(substitutionParamNodes);

        astPatternNodeMap.clear();
    }

    /**
     * End processing of the AST tree, check that expression nodes found their homes.
     * @throws ASTWalkException is the walk failed
     */
    protected void end() throws ASTWalkException {
        log.debug(".end");

        if (astExprNodeMap.size() > 1) {
            throw new ASTWalkException("Unexpected AST tree contains left over child elements,"
                    + " not all expression nodes have been removed from AST-to-expression nodes map");
        }
        if (astPatternNodeMap.size() > 1) {
            throw new ASTWalkException("Unexpected AST tree contains left over child elements,"
                    + " not all pattern nodes have been removed from AST-to-pattern nodes map");
        }

        statementSpec.setSubstitutionParameters(substitutionParamNodes);
    }

    private void leaveSelectionElement(Tree node) throws ASTWalkException {
        log.debug(".leaveSelectionElement");

        if ((astExprNodeMap.size() > 1) || ((astExprNodeMap.isEmpty()))) {
            throw new ASTWalkException("Unexpected AST tree contains zero or more then 1 child element for root");
        }

        // Get expression node sub-tree from the AST nodes placed so far
        ExprNode exprNode = astExprNodeMap.values().iterator().next();
        astExprNodeMap.clear();

        // Get list element name
        String optionalName = null;
        if (node.getChildCount() > 1 && node.getChild(1).getType() == IDENT) {
            optionalName = node.getChild(1).getText();
        }

        boolean eventsAnnotation = false;
        Tree atChar = ASTUtil.findFirstNode(node, ATCHAR);
        if (atChar != null) {
            String annotation = atChar.getChild(0).getText().trim().toLowerCase();
            if (annotation.equals("eventbean") || annotation.equals("eventbean")) {
                eventsAnnotation = true;
            } else {
                throw new ASTWalkException("Failed to recognize select-expression annotation '" + annotation
                        + "', expected 'eventbean'");
            }
        }

        // Add as selection element
        statementSpec.getSelectClauseSpec()
                .add(new SelectClauseExprRawSpec(exprNode, optionalName, eventsAnnotation));
    }

    private void leavePropertySelectionElement(Tree node) throws ASTWalkException {
        log.debug(".leavePropertySelectionElement");

        if ((astExprNodeMap.size() > 1) || ((astExprNodeMap.isEmpty()))) {
            throw new ASTWalkException("Unexpected AST tree contains zero or more then 1 child element for root");
        }

        // Get expression node sub-tree from the AST nodes placed so far
        ExprNode exprNode = astExprNodeMap.values().iterator().next();
        astExprNodeMap.clear();

        // Get list element name
        String optionalName = null;
        if (node.getChildCount() > 1) {
            optionalName = node.getChild(1).getText();
        }

        // Add as selection element
        if (propertySelectRaw == null) {
            propertySelectRaw = new ArrayList<SelectClauseElementRaw>();
        }
        this.propertySelectRaw.add(new SelectClauseExprRawSpec(exprNode, optionalName, false));
    }

    private void leavePropertySelectionStream(Tree node) throws ASTWalkException {
        log.debug(".leavePropertySelectionStream");

        String streamName = node.getChild(0).getText();

        // Get element name
        String optionalName = null;
        if (node.getChildCount() > 1) {
            optionalName = node.getChild(1).getText();
        }

        // Add as selection element
        if (propertySelectRaw == null) {
            propertySelectRaw = new ArrayList<SelectClauseElementRaw>();
        }
        this.propertySelectRaw.add(new SelectClauseStreamRawSpec(streamName, optionalName));
    }

    private void leaveSelectionStream(Tree node) throws ASTWalkException {
        log.debug(".leaveSelectionStream");

        String streamName = node.getChild(0).getText();

        // Get element name
        String optionalName = null;
        if (node.getChildCount() > 1) {
            optionalName = node.getChild(1).getText();
        }

        // Add as selection element
        statementSpec.getSelectClauseSpec().add(new SelectClauseStreamRawSpec(streamName, optionalName));
    }

    private void leaveWildcardSelect() {
        log.debug(".leaveWildcardSelect");
        statementSpec.getSelectClauseSpec().add(new SelectClauseElementWildcard());
    }

    private void leavePropertyWildcardSelect() {
        log.debug(".leavePropertyWildcardSelect");
        if (propertySelectRaw == null) {
            propertySelectRaw = new ArrayList<SelectClauseElementRaw>();
        }
        this.propertySelectRaw.add(new SelectClauseElementWildcard());
    }

    private void leavePropertySelectAtom(Tree node) {
        log.debug(".leavePropertySelectAtom");

        // initialize if not set
        if (propertyEvalSpec == null) {
            propertyEvalSpec = new PropertyEvalSpec();
        }

        // get select clause
        SelectClauseSpecRaw optionalSelectClause = new SelectClauseSpecRaw();
        if (propertySelectRaw != null) {
            optionalSelectClause.getSelectExprList().addAll(propertySelectRaw);
            propertySelectRaw = null;
        }

        // get the splitter expression
        ExprNode splitterExpression;
        if (node.getChild(0).getType() == SELECT) {
            splitterExpression = astExprNodeMap.remove(node.getChild(1));
        } else {
            splitterExpression = astExprNodeMap.remove(node.getChild(0));
        }

        // get where-clause, if any
        Tree optionalWhereClauseTree = ASTUtil.findFirstNode(node, WHERE_EXPR);
        ExprNode optionalWhereClause = optionalWhereClauseTree == null ? null
                : astExprNodeMap.remove(optionalWhereClauseTree.getChild(0));

        Tree propertyAsNameTree = ASTUtil.findFirstNode(node, IDENT);
        String optionalAsName = propertyAsNameTree == null ? null : propertyAsNameTree.getText();

        String splitterEventTypeName = null;
        Tree splitterEventTypeNameNode = ASTUtil.findFirstNode(node, ATCHAR);
        if (splitterEventTypeNameNode != null && splitterEventTypeNameNode.getChild(0).getText().equals("type")) {
            splitterEventTypeName = splitterEventTypeNameNode.getChild(1).getText();
        }

        PropertyEvalAtom atom = new PropertyEvalAtom(splitterExpression, splitterEventTypeName, optionalAsName,
                optionalSelectClause, optionalWhereClause);
        propertyEvalSpec.add(atom);
    }

    private void leaveView(Tree node) throws ASTWalkException {
        log.debug(".leaveView");
        String objectNamespace = node.getChild(0).getText();
        String objectName = node.getChild(1).getText();
        List<ExprNode> viewParameters = ASTExprHelper.getExprNodes(node, 2, astExprNodeMap);
        viewSpecs.add(new ViewSpec(objectNamespace, objectName, viewParameters));
    }

    private void leaveMatchRecognizeMeasureItem(Tree node) throws ASTWalkException {
        log.debug(".leaveMatchRecognizeMeasureItem");

        if (statementSpec.getMatchRecognizeSpec() == null) {
            statementSpec.setMatchRecognizeSpec(new MatchRecognizeSpec());
        }

        Tree currentNode = node.getChild(0);
        ExprNode exprNode = astExprNodeMap.get(currentNode);
        if (exprNode == null) {
            throw new IllegalStateException("Expression node for AST node not found for type "
                    + currentNode.getType() + " and text " + currentNode.getText());
        }
        astExprNodeMap.remove(currentNode);

        String name = null;
        if (node.getChildCount() > 1) {
            name = node.getChild(1).getText();
        }
        statementSpec.getMatchRecognizeSpec().addMeasureItem(new MatchRecognizeMeasureItem(exprNode, name));
    }

    private void leaveMatchRecognizePatternAtom(Tree node) throws ASTWalkException {
        log.debug(".leaveMatchRecognizePatternAtom");

        String first = node.getChild(0).getText();
        RegexNFATypeEnum type = RegexNFATypeEnum.SINGLE;
        if (node.getChildCount() > 2) {
            type = RegexNFATypeEnum.fromString(node.getChild(1).getText(), node.getChild(2).getText());
        } else if (node.getChildCount() > 1) {
            type = RegexNFATypeEnum.fromString(node.getChild(1).getText(), null);
        }

        RowRegexExprNodeAtom item = new RowRegexExprNodeAtom(first, type);
        astRowRegexNodeMap.put(node, item);
    }

    private void leaveMatchRecognizePatternAlter(Tree node) throws ASTWalkException {
        log.debug(".leaveMatchRecognizePatternAlter");

        RowRegexExprNodeAlteration alterNode = new RowRegexExprNodeAlteration();
        astRowRegexNodeMap.put(node, alterNode);
    }

    private void leaveMatchRecognizePatternConcat(Tree node) throws ASTWalkException {
        RowRegexExprNodeConcatenation concatNode = new RowRegexExprNodeConcatenation();
        astRowRegexNodeMap.put(node, concatNode);
    }

    private void leaveMatchRecognizePatternNested(Tree node) throws ASTWalkException {
        RegexNFATypeEnum type = RegexNFATypeEnum.SINGLE;
        if (node.getChildCount() > 2) {
            type = RegexNFATypeEnum.fromString(node.getChild(1).getText(), node.getChild(2).getText());
        } else if (node.getChildCount() > 1) {
            type = RegexNFATypeEnum.fromString(node.getChild(1).getText(), null);
        }
        RowRegexExprNodeNested nestedNode = new RowRegexExprNodeNested(type);
        astRowRegexNodeMap.put(node, nestedNode);
    }

    private void leaveMatchRecognizePattern(Tree node) throws ASTWalkException {
        Tree currentNode = node.getChild(0);
        RowRegexExprNode exprNode = this.astRowRegexNodeMap.get(currentNode);
        if (exprNode == null) {
            throw new IllegalStateException("Expression node for AST node not found for type "
                    + currentNode.getType() + " and text " + currentNode.getText());
        }
        astRowRegexNodeMap.remove(currentNode);
        statementSpec.getMatchRecognizeSpec().setPattern(exprNode);
    }

    private void leaveMatchRecognizeDefineItem(Tree node) throws ASTWalkException {
        log.debug(".leaveMatchRecognizeDefineItem");
        String first = node.getChild(0).getText();

        Tree currentNode = node.getChild(1);
        ExprNode exprNode = astExprNodeMap.get(currentNode);
        if (exprNode == null) {
            throw new IllegalStateException("Expression node for AST node not found for type "
                    + currentNode.getType() + " and text " + currentNode.getText());
        }
        astExprNodeMap.remove(currentNode);
        statementSpec.getMatchRecognizeSpec().getDefines().add(new MatchRecognizeDefineItem(first, exprNode));
    }

    private void leaveMatchRecognize(Tree node) throws ASTWalkException {
        log.debug(".leaveMatchRecognize");

        boolean allMatches = false;
        for (int i = 0; i < node.getChildCount(); i++) {
            if (node.getChild(i).getType() == ALL) {
                allMatches = true;
            }
        }

        MatchRecognizeSkipEnum skip;
        for (int i = 0; i < node.getChildCount(); i++) {
            if (node.getChild(i).getType() == MATCHREC_AFTER_SKIP) {
                skip = ASTMatchRecognizeHelper.parseSkip(node.getChild(i));
                statementSpec.getMatchRecognizeSpec().getSkip().setSkip(skip);
            }
        }

        for (int i = 0; i < node.getChildCount(); i++) {
            if (node.getChild(i).getType() == MATCHREC_INTERVAL) {
                Tree intervalParent = node.getChild(i);
                if (!intervalParent.getChild(0).getText().toLowerCase().equals("interval")) {
                    throw new ASTWalkException(
                            "Invalid interval-clause within match-recognize, expecting keyword INTERVAL");
                }
                ExprNode expression = astExprNodeMap.remove(intervalParent.getChild(1));
                ExprTimePeriod timePeriodExpr = (ExprTimePeriod) expression;
                statementSpec.getMatchRecognizeSpec().setInterval(new MatchRecognizeInterval(timePeriodExpr));
            }
        }

        statementSpec.getMatchRecognizeSpec().setAllMatches(allMatches);
    }

    private void leaveOnSelect(Tree node) throws ASTWalkException {
        log.debug(".leaveOnSelect");

        for (int i = 0; i < node.getChildCount(); i++) {
            if (node.getChild(i).getType() == DISTINCT) {
                statementSpec.getSelectClauseSpec().setDistinct(true);
            }
        }
    }

    private void leaveMatchRecognizePartition(Tree node) throws ASTWalkException {
        log.debug(".leaveMatchRecognizePartition");
        if (statementSpec.getMatchRecognizeSpec() == null) {
            statementSpec.setMatchRecognizeSpec(new MatchRecognizeSpec());
        }
        statementSpec.getMatchRecognizeSpec().getPartitionByExpressions()
                .addAll(ASTExprHelper.getExprNodes(node, 0, astExprNodeMap));
    }

    private void leaveStreamExpr(Tree node) {
        log.debug(".leaveStreamExpr");

        // Determine the optional stream name
        // Search for identifier node that carries the stream name in an "from Class.win:time().std:getEnumerationSource() as StreamName"
        Tree streamNameNode = null;
        for (int i = 1; i < node.getChildCount(); i++) {
            Tree child = node.getChild(i);
            if (child.getType() == IDENT) {
                streamNameNode = child;
                break;
            }
        }
        String streamName = null;
        if (streamNameNode != null) {
            streamName = streamNameNode.getText();
        }

        // The first child node may be a "stream" keyword
        boolean isUnidirectional = false;
        boolean isRetainUnion = false;
        boolean isRetainIntersection = false;
        for (int i = 0; i < node.getChildCount(); i++) {
            if (node.getChild(i).getType() == UNIDIRECTIONAL) {
                isUnidirectional = true;
                break;
            }
            if (node.getChild(i).getType() == RETAINUNION) {
                isRetainUnion = true;
                break;
            }
            if (node.getChild(i).getType() == RETAININTERSECTION) {
                isRetainIntersection = true;
                break;
            }
        }

        // Convert to a stream specification instance
        StreamSpecRaw streamSpec;
        StreamSpecOptions options = new StreamSpecOptions(isUnidirectional, isRetainUnion, isRetainIntersection);

        // If the first subnode is a filter node, we have a filter stream specification
        if (node.getChild(0).getType() == EVENT_FILTER_EXPR) {
            streamSpec = new FilterStreamSpecRaw(filterSpec, ViewSpec.toArray(viewSpecs), streamName, options);
        } else if (node.getChild(0).getType() == PATTERN_INCL_EXPR) {
            if ((astPatternNodeMap.size() > 1) || ((astPatternNodeMap.isEmpty()))) {
                throw new ASTWalkException(
                        "Unexpected AST tree contains zero or more then 1 child elements for root");
            }

            // Get expression node sub-tree from the AST nodes placed so far
            EvalFactoryNode evalNode = astPatternNodeMap.values().iterator().next();

            streamSpec = new PatternStreamSpecRaw(evalNode, evalNodeExpressions, ViewSpec.toArray(viewSpecs),
                    streamName, options);
            if (evalNodeExpressions != null) {
                evalNodeExpressions = new HashMap<EvalFactoryNode, String>();
            }
            astPatternNodeMap.clear();
        } else if (node.getChild(0).getType() == DATABASE_JOIN_EXPR) {
            Tree dbrootNode = node.getChild(0);
            String dbName = dbrootNode.getChild(0).getText();
            String sqlWithParams = StringValue.parseString(dbrootNode.getChild(1).getText().trim());

            // determine if there is variables used
            List<PlaceholderParser.Fragment> sqlFragments;
            try {
                sqlFragments = PlaceholderParser.parsePlaceholder(sqlWithParams);
                for (PlaceholderParser.Fragment fragment : sqlFragments) {
                    if (!(fragment instanceof PlaceholderParser.ParameterFragment)) {
                        continue;
                    }

                    // Parse expression, store for substitution parameters
                    String expression = fragment.getValue();
                    if (expression.toUpperCase()
                            .equals(DatabasePollingViewableFactory.SAMPLE_WHERECLAUSE_PLACEHOLDER)) {
                        continue;
                    }

                    if (expression.trim().length() == 0) {
                        throw new ASTWalkException("Missing expression within ${...} in SQL statement");
                    }
                    String toCompile = "select * from java.lang.Object where " + expression;
                    StatementSpecRaw raw = EPAdministratorHelper.compileEPL(toCompile, expression, false, null,
                            SelectClauseStreamSelectorEnum.ISTREAM_ONLY, engineImportService, variableService,
                            schedulingService, engineURI, configurationInformation, patternNodeFactory,
                            contextManagementService, exprDeclaredService);

                    if ((raw.getSubstitutionParameters() != null) && (raw.getSubstitutionParameters().size() > 0)) {
                        throw new ASTWalkException(
                                "EPL substitution parameters are not allowed in SQL ${...} expressions, consider using a variable instead");
                    }

                    if (raw.isHasVariables()) {
                        statementSpec.setHasVariables(true);
                    }

                    // add expression
                    if (statementSpec.getSqlParameters() == null) {
                        statementSpec.setSqlParameters(new HashMap<Integer, List<ExprNode>>());
                    }
                    List<ExprNode> listExp = statementSpec.getSqlParameters()
                            .get(statementSpec.getStreamSpecs().size());
                    if (listExp == null) {
                        listExp = new ArrayList<ExprNode>();
                        statementSpec.getSqlParameters().put(statementSpec.getStreamSpecs().size(), listExp);
                    }
                    listExp.add(raw.getFilterRootNode());
                }
            } catch (PlaceholderParseException ex) {
                log.warn("Failed to parse SQL text '" + sqlWithParams + "' :" + ex.getMessage());
                // Let the view construction handle the validation
            }

            String sampleSQL = null;
            if (dbrootNode.getChildCount() > 2) {
                sampleSQL = dbrootNode.getChild(2).getText();
                sampleSQL = StringValue.parseString(sampleSQL.trim());
            }

            streamSpec = new DBStatementStreamSpec(streamName, ViewSpec.toArray(viewSpecs), dbName, sqlWithParams,
                    sampleSQL);
        } else if (node.getChild(0).getType() == METHOD_JOIN_EXPR) {
            Tree methodRootNode = node.getChild(0);
            String prefixIdent = methodRootNode.getChild(0).getText();
            String className = methodRootNode.getChild(1).getText();

            int indexDot = className.lastIndexOf('.');
            String classNamePart;
            String methodNamePart;
            if (indexDot == -1) {
                classNamePart = className;
                methodNamePart = null;
            } else {
                classNamePart = className.substring(0, indexDot);
                methodNamePart = className.substring(indexDot + 1);
            }
            List<ExprNode> exprNodes = ASTExprHelper.getExprNodes(methodRootNode, 2, astExprNodeMap);

            streamSpec = new MethodStreamSpec(streamName, ViewSpec.toArray(viewSpecs), prefixIdent, classNamePart,
                    methodNamePart, exprNodes);
        } else {
            throw new ASTWalkException(
                    "Unexpected AST child node to stream expression, type=" + node.getChild(0).getType());
        }
        viewSpecs.clear();
        statementSpec.getStreamSpecs().add(streamSpec);
    }

    private void leaveEventPropertyExpr(Tree node) {
        log.debug(".leaveEventPropertyExpr");

        if (node.getChildCount() == 0) {
            throw new IllegalStateException("Empty event property expression encountered");
        }

        ExprNode exprNode;
        String propertyName;

        // The stream name may precede the event property name, but cannot be told apart from the property name:
        //      s0.p1 could be a nested property, or could be stream 's0' and property 'p1'

        // A single entry means this must be the property name.
        // And a non-simple property means that it cannot be a stream name.
        if ((node.getChildCount() == 1) || (node.getChild(0).getType() != EVENT_PROP_SIMPLE)) {
            propertyName = ASTFilterSpecHelper.getPropertyName(node, 0);
            exprNode = new ExprIdentNodeImpl(propertyName);

            Pair<String, String> mappedPropertyPair = ASTFilterSpecHelper.getMappedPropertyPair(node);
            if (mappedPropertyPair != null) {
                List<ExprNode> params = Collections
                        .<ExprNode>singletonList(new ExprConstantNodeImpl(mappedPropertyPair.getSecond()));
                ExprNodeScript scriptNode = ExprDeclaredHelper.getExistsScript(getDefaultDialect(),
                        mappedPropertyPair.getFirst(), params, scriptExpressions, exprDeclaredService);
                if (scriptNode != null) {
                    exprNode = scriptNode;
                }
            }
        }
        // --> this is more then one child node, and the first child node is a simple property
        // we may have a stream name in the first simple property, or a nested property
        // i.e. 's0.p0' could mean that the event has a nested property to 's0' of name 'p0', or 's0' is the stream name
        else {
            String leadingIdentifier = node.getChild(0).getChild(0).getText();
            String streamOrNestedPropertyName = ASTFilterSpecHelper.escapeDot(leadingIdentifier);
            propertyName = ASTFilterSpecHelper.getPropertyName(node, 1);

            if (variableService.getReader(leadingIdentifier) != null) {
                exprNode = new ExprVariableNodeImpl(leadingIdentifier + "." + propertyName, variableService);
                statementSpec.setHasVariables(true);
                addVariable(statementSpec, propertyName);
            } else if (contextDescriptor != null && contextDescriptor.getContextPropertyRegistry()
                    .isContextPropertyPrefix(streamOrNestedPropertyName)) {
                exprNode = new ExprContextPropertyNode(propertyName);
            } else {
                exprNode = new ExprIdentNodeImpl(propertyName, streamOrNestedPropertyName);
            }
        }

        if (variableService.getReader(propertyName) != null) {
            exprNode = new ExprVariableNodeImpl(propertyName, variableService);
            statementSpec.setHasVariables(true);
            addVariable(statementSpec, propertyName);
        }

        astExprNodeMap.put(node, exprNode);
    }

    private void addVariable(StatementSpecRaw statementSpec, String propertyName) {
        if (statementSpec.getReferencedVariables() == null) {
            statementSpec.setReferencedVariables(new HashSet<String>());
        }
        statementSpec.getReferencedVariables().add(propertyName);
    }

    private void leaveLibFunctionSingleChild(Tree parent, Tree node) {
        log.debug(".leaveLibFunctionOld");

        String childNodeText = node.getChild(0).getText();
        if (node.getChild(0).getType() == CLASS_IDENT) {
            String className = node.getChild(0).getText();
            List<ExprChainedSpec> chained = getLibFuncChain(parent);
            chained.add(0, new ExprChainedSpec(className, Collections.<ExprNode>emptyList(), true));
            astExprNodeMap.put(node,
                    new ExprDotNode(chained,
                            configurationInformation.getEngineDefaults().getExpression().isDuckTyping(),
                            configurationInformation.getEngineDefaults().getExpression().isUdfCache()));
            return;
        }

        boolean isDistinct = false;
        if ((node.getChild(1) != null) && (node.getChild(1).getType() == DISTINCT)) {
            isDistinct = true;
        }

        // try plug-in single-row function
        try {
            Pair<Class, EngineImportSingleRowDesc> classMethodPair = engineImportService
                    .resolveSingleRow(childNodeText);
            List<ExprChainedSpec> spec = new ArrayList<ExprChainedSpec>();
            List<ExprNode> childExpressions = ASTLibHelper.getExprNodesLibFunc(0, node, astExprNodeMap);
            spec.add(new ExprChainedSpec(classMethodPair.getSecond().getMethodName(), childExpressions, true));
            astExprNodeMap.put(node, new ExprPlugInSingleRowNode(childNodeText, classMethodPair.getFirst(), spec,
                    classMethodPair.getSecond()));
            return;
        } catch (EngineImportUndefinedException e) {
            // Not an single-row function
        } catch (EngineImportException e) {
            throw new IllegalStateException("Error resolving single-row function: " + e.getMessage(), e);
        }

        // try plug-in aggregation function
        ExprNode aggregationNode = ASTAggregationHelper.tryResolveAsAggregation(engineImportService, isDistinct,
                childNodeText, plugInAggregations, engineURI);
        if (aggregationNode != null) {
            astExprNodeMap.put(node, aggregationNode);
            return;
        }

        // special case for min,max
        if ((childNodeText.toLowerCase().equals("max")) || (childNodeText.toLowerCase().equals("min"))
                || (childNodeText.toLowerCase().equals("fmax")) || (childNodeText.toLowerCase().equals("fmin"))) {
            handleMinMax(node);
            return;
        }

        // try expression declaration local statement
        String expressionName = node.getChild(0).getText();
        List<ExprChainedSpec> expressionChain = getLibFuncChain(parent);
        ExprDeclaredNodeImpl declaredNode = ExprDeclaredHelper.getExistsDeclaredExpr(expressionName,
                expressionChain.get(0).getParameters(), expressionDeclarations.getExpressions(),
                exprDeclaredService);
        if (declaredNode != null) {
            astExprNodeMap.put(node, declaredNode);
            return;
        }

        // try scripting expression
        ExprNodeScript scriptNode = ExprDeclaredHelper.getExistsScript(getDefaultDialect(), expressionName,
                expressionChain.get(0).getParameters(), scriptExpressions, exprDeclaredService);
        if (scriptNode != null) {
            astExprNodeMap.put(node, scriptNode);
            return;
        }

        // Could be a mapped property with an expression-parameter "mapped(expr)" or array property with an expression-parameter "array(expr)".
        astExprNodeMap.put(node, new ExprDotNode(Collections.singletonList(expressionChain.get(0)), false, false));
    }

    private void leaveDotExpr(Tree node) {
        log.debug(".leaveDotExpr");
        List<ExprChainedSpec> chainSpec = getLibFuncChain(node);
        astExprNodeMap.put(node,
                new ExprDotNode(chainSpec,
                        configurationInformation.getEngineDefaults().getExpression().isDuckTyping(),
                        configurationInformation.getEngineDefaults().getExpression().isUdfCache()));
    }

    private void leaveLibFunctionChain(Tree node) {
        log.debug(".leaveLibFunctionChain");

        // Single chain can include a class name or property name.
        // As the current node does not generate any expression for this 1-element chain, forward expression to this node.
        if (node.getChildCount() == 1) {
            leaveLibFunctionSingleChild(node, node.getChild(0));
            mapChildASTToChildExprNode(node.getChild(0));
            ExprNode generated = astExprNodeMap.remove(node.getChild(0));
            astExprNodeMap.put(node, generated);
            return;
        }

        String className = node.getChild(0).getChild(0).getText();
        List<ExprChainedSpec> chained = this.getLibFuncChain(node);

        // try plug-in single-row function
        try {
            Pair<Class, EngineImportSingleRowDesc> classMethodPair = engineImportService
                    .resolveSingleRow(className);
            chained.get(0).setName(classMethodPair.getSecond().getMethodName());
            astExprNodeMap.put(node, new ExprPlugInSingleRowNode(className, classMethodPair.getFirst(), chained,
                    classMethodPair.getSecond()));
            return;
        } catch (EngineImportUndefinedException e) {
            // Not an single-row function
        } catch (EngineImportException e) {
            throw new IllegalStateException("Error resolving single-row function: " + e.getMessage(), e);
        }

        // if the class name is the first
        boolean duckType = configurationInformation.getEngineDefaults().getExpression().isDuckTyping();
        boolean udfCache = configurationInformation.getEngineDefaults().getExpression().isUdfCache();
        if (!className.equals(chained.get(0).getName())) {
            chained.add(0, new ExprChainedSpec(className, Collections.<ExprNode>emptyList(), true));
        }

        ExprDotNode dotNode = new ExprDotNode(chained, duckType, udfCache);

        // try expression declaration local statement
        String name = chained.get(0).getName();
        ExprDeclaredNodeImpl declaredNode = ExprDeclaredHelper.getExistsDeclaredExpr(name,
                chained.get(0).getParameters(), expressionDeclarations.getExpressions(), exprDeclaredService);
        if (declaredNode != null) {
            dotNode.addChildNode(declaredNode);
            chained.remove(0);
        } else {
            ExprNodeScript scriptNode = ExprDeclaredHelper.getExistsScript(getDefaultDialect(), name,
                    chained.get(0).getParameters(), scriptExpressions, exprDeclaredService);
            if (scriptNode != null) {
                dotNode.addChildNode(scriptNode);
                chained.remove(0);
            }
        }

        // try aggregation function
        // try plug-in aggregation function
        boolean isDistinct = false;
        if ((node.getChildCount() > 1 && node.getChild(1) != null) && (node.getChild(1).getType() == DISTINCT)) {
            isDistinct = true;
        }
        ExprNode aggregationNode = ASTAggregationHelper.tryResolveAsAggregation(engineImportService, isDistinct,
                name, plugInAggregations, engineURI);
        if (aggregationNode != null) {
            dotNode.addChildNode(aggregationNode);
            ExprChainedSpec first = chained.remove(0);
            aggregationNode.addChildNodes(first.getParameters());
        }

        astExprNodeMap.put(node, dotNode);
    }

    private void leaveEqualsExpr(Tree node) {
        log.debug(".leaveEqualsExpr");

        boolean isNot = false;
        if (node.getType() == EVAL_NOTEQUALS_EXPR || node.getType() == EVAL_ISNOT_EXPR) {
            isNot = true;
        }

        boolean isIs = false;
        if (node.getType() == EVAL_IS_EXPR || node.getType() == EVAL_ISNOT_EXPR) {
            isIs = true;
        }

        ExprEqualsNode identNode = new ExprEqualsNodeImpl(isNot, isIs);
        astExprNodeMap.put(node, identNode);
    }

    private void leaveEqualsGroupExpr(Tree node) {
        log.debug(".leaveEqualsGroupExpr");

        boolean isNot = false;
        if (node.getType() == EVAL_NOTEQUALS_GROUP_EXPR) {
            isNot = true;
        }

        boolean isAll = false;
        if (node.getChild(1).getType() == ALL) {
            isAll = true;
        }

        if ((node.getChildCount() > 2) && (node.getChild(2).getType() == SUBSELECT_GROUP_EXPR)) {
            StatementSpecRaw currentSpec = popStacks();
            ExprSubselectAllSomeAnyNode subselectNode = new ExprSubselectAllSomeAnyNode(currentSpec, isNot, isAll,
                    null);
            astExprNodeMap.put(node, subselectNode);
        } else {
            ExprEqualsAllAnyNode groupNode = new ExprEqualsAllAnyNode(isNot, isAll);
            astExprNodeMap.put(node, groupNode);
        }
    }

    private void leaveJoinAndExpr(Tree node) {
        log.debug(".leaveJoinAndExpr");
        ExprAndNode identNode = new ExprAndNodeImpl();
        astExprNodeMap.put(node, identNode);
    }

    private void leaveJoinOrExpr(Tree node) {
        log.debug(".leaveJoinOrExpr");
        ExprOrNode identNode = new ExprOrNode();
        astExprNodeMap.put(node, identNode);
    }

    private void leaveConstant(Tree node) {
        log.debug(".leaveConstant value '" + node.getText() + "'");
        ExprConstantNode constantNode = new ExprConstantNodeImpl(ASTConstantHelper.parse(node));
        astExprNodeMap.put(node, constantNode);
    }

    private void leaveJsonConstant(Tree node) {
        log.debug(".leaveJsonConstant value '" + node.getText() + "'");
        ExprConstantNode constantNode = new ExprConstantNodeImpl(ASTJsonHelper.walk(node));
        astExprNodeMap.put(node, constantNode);
    }

    private void leaveFafDelete(Tree node) {
        log.debug(".leaveFafDelete value '" + node.getText() + "'");
        handleNamedWindowStream(node);
        statementSpec.setFireAndForgetSpec(new FireAndForgetSpecDelete());
    }

    private void leaveFafUpdate(Tree node) {
        log.debug(".leaveFafUpdate value '" + node.getText() + "'");
        handleNamedWindowStream(node.getChild(0));
        List<OnTriggerSetAssignment> assignments = ASTExprHelper.getOnTriggerSetAssignments(node.getChild(0),
                astExprNodeMap);
        ExprNode whereClause = this.getRemoveFirstByType(node.getChild(0), WHERE_EXPR);
        statementSpec.setFilterExprRootNode(whereClause);
        statementSpec.setFireAndForgetSpec(new FireAndForgetSpecUpdate(assignments));
    }

    private void handleNamedWindowStream(Tree node) {
        String windowName = node.getChild(0).getText();
        String alias = null;
        if (node.getChildCount() > 1 && node.getChild(1).getType() == IDENT) {
            alias = node.getChild(1).getText();
        }
        statementSpec.getStreamSpecs()
                .add(new FilterStreamSpecRaw(new FilterSpecRaw(windowName, Collections.<ExprNode>emptyList(), null),
                        ViewSpec.toArray(viewSpecs), alias, new StreamSpecOptions()));
    }

    private void leaveSubstitution(Tree node) {
        log.debug(".leaveSubstitution");

        // Add the substitution parameter node, for later replacement
        int currentSize = this.substitutionParamNodes.size();
        ExprSubstitutionNode substitutionNode = new ExprSubstitutionNode(currentSize + 1);
        substitutionParamNodes.add(substitutionNode);

        astExprNodeMap.put(node, substitutionNode);
    }

    private void leaveMath(Tree node) {
        log.debug(".leaveMath");

        MathArithTypeEnum mathArithTypeEnum;

        switch (node.getType()) {
        case DIV:
            mathArithTypeEnum = MathArithTypeEnum.DIVIDE;
            break;
        case STAR:
            mathArithTypeEnum = MathArithTypeEnum.MULTIPLY;
            break;
        case PLUS:
            mathArithTypeEnum = MathArithTypeEnum.ADD;
            break;
        case MINUS:
            mathArithTypeEnum = MathArithTypeEnum.SUBTRACT;
            break;
        case MOD:
            mathArithTypeEnum = MathArithTypeEnum.MODULO;
            break;
        default:
            throw new IllegalArgumentException("Node type " + node.getType() + " not a recognized math node type");
        }

        ExprMathNode mathNode = new ExprMathNode(mathArithTypeEnum,
                configurationInformation.getEngineDefaults().getExpression().isIntegerDivision(),
                configurationInformation.getEngineDefaults().getExpression().isDivisionByZeroReturnsNull());
        astExprNodeMap.put(node, mathNode);
    }

    // Min/Max nodes can be either an aggregate or a per-row function depending on the number or arguments
    private void handleMinMax(Tree libNode) {
        log.debug(".handleMinMax");

        // Determine min or max
        Tree childNode = libNode.getChild(0);
        String childNodeText = childNode.getText().toLowerCase();
        MinMaxTypeEnum minMaxTypeEnum;
        boolean filtered = childNodeText.startsWith("f");
        if (childNodeText.equals("min") || childNodeText.equals("fmin")) {
            minMaxTypeEnum = MinMaxTypeEnum.MIN;
        } else if (childNodeText.equals("max") || childNodeText.equals("fmax")) {
            minMaxTypeEnum = MinMaxTypeEnum.MAX;
        } else {
            throw new IllegalArgumentException("Node type " + childNode.getType() + ' ' + childNode.getText()
                    + " not a recognized min max node");
        }

        // Determine distinct or not
        Tree nextNode = libNode.getChild(1);
        boolean isDistinct = false;
        if (nextNode.getType() == DISTINCT) {
            isDistinct = true;
        }

        // Error if more then 3 nodes with distinct since it's an aggregate function
        if ((libNode.getChildCount() > 4) && (isDistinct) && !filtered) {
            throw new ASTWalkException("The distinct keyword is not valid in per-row min and max "
                    + "functions with multiple sub-expressions");
        }

        ExprNode minMaxNode;
        if ((!isDistinct) && (libNode.getChildCount() > 3) && !filtered) {
            // use the row function
            minMaxNode = new ExprMinMaxRowNode(minMaxTypeEnum);
        } else {
            // use the aggregation function
            minMaxNode = new ExprMinMaxAggrNode(isDistinct, minMaxTypeEnum, filtered);
        }
        astExprNodeMap.put(libNode, minMaxNode);
    }

    private void leaveCoalesce(Tree node) {
        log.debug(".leaveCoalesce");

        ExprNode coalesceNode = new ExprCoalesceNode();
        astExprNodeMap.put(node, coalesceNode);
    }

    private void leaveAggregate(Tree node) {
        log.debug(".leaveAggregate");

        boolean isDistinct = false;
        if ((node.getChild(0) != null) && (node.getChild(0).getType() == DISTINCT)) {
            isDistinct = true;
        }

        // NOTE: Also see "postLeaveAggregate" below which appends the filter expression
        boolean hasFilter = ASTUtil.findFirstNode(node, AGG_FILTER_EXPR) != null;

        ExprAggregateNode aggregateNode;
        ExprNode childNode = null;

        switch (node.getType()) {
        case AVG:
            aggregateNode = new ExprAvgNode(isDistinct, hasFilter);
            break;
        case SUM:
            aggregateNode = new ExprSumNode(isDistinct, hasFilter);
            break;
        case COUNT:
            aggregateNode = new ExprCountNode(isDistinct, hasFilter);
            break;
        case MEDIAN:
            aggregateNode = new ExprMedianNode(isDistinct, hasFilter);
            break;
        case STDDEV:
            aggregateNode = new ExprStddevNode(isDistinct, hasFilter);
            break;
        case AVEDEV:
            aggregateNode = new ExprAvedevNode(isDistinct, hasFilter);
            break;
        case FIRST_AGGREG:
        case WINDOW_AGGREG:
        case LAST_AGGREG:
            boolean isWildcard = false;
            String streamWildcard = null;
            if (node.getChildCount() > 0 && node.getChild(0).getType() == ACCESS_AGG) {
                Tree wildcardNode = ASTUtil.findFirstNode(node.getChild(0), PROPERTY_WILDCARD_SELECT);
                Tree streamWCNode = ASTUtil.findFirstNode(node.getChild(0), PROPERTY_SELECTION_STREAM);
                if (wildcardNode != null) {
                    isWildcard = true;
                } else if (streamWCNode != null) {
                    streamWildcard = streamWCNode.getChild(0).getText();
                } else {
                    childNode = astExprNodeMap.remove(node.getChild(0).getChild(0));
                }
            } else { // no parameter case
                isWildcard = true;
            }

            if (node.getType() == FIRST_AGGREG) {
                aggregateNode = new ExprAggMultiFunctionLinearAccessNode(AggregationStateType.FIRST, isWildcard,
                        streamWildcard);
            } else if (node.getType() == WINDOW_AGGREG) {
                aggregateNode = new ExprAggMultiFunctionLinearAccessNode(AggregationStateType.WINDOW, isWildcard,
                        streamWildcard);
            } else {
                aggregateNode = new ExprAggMultiFunctionLinearAccessNode(AggregationStateType.LAST, isWildcard,
                        streamWildcard);
            }
            break;
        default:
            throw new IllegalArgumentException(
                    "Node type " + node.getType() + " not a recognized aggregate node type");
        }

        if (childNode != null) {
            aggregateNode.addChildNode(childNode);
        }
        astExprNodeMap.put(node, aggregateNode);
    }

    private void postLeaveAggregate(Tree node) {
        Tree optionalFilterNode = ASTUtil.findFirstNode(node, AGG_FILTER_EXPR);
        if (optionalFilterNode == null) {
            return;
        }
        ExprNode currentAggNode = astExprNodeMap.get(node);
        ExprNode filter = astExprNodeMap.remove(optionalFilterNode.getChild(0));
        currentAggNode.addChildNode(filter);
    }

    private void leaveRelationalOp(Tree node) {
        log.debug(".leaveRelationalOp");

        RelationalOpEnum relationalOpEnum;

        switch (node.getType()) {
        case LT:
            relationalOpEnum = RelationalOpEnum.LT;
            break;
        case GT:
            relationalOpEnum = RelationalOpEnum.GT;
            break;
        case LE:
            relationalOpEnum = RelationalOpEnum.LE;
            break;
        case GE:
            relationalOpEnum = RelationalOpEnum.GE;
            break;
        default:
            throw new IllegalArgumentException(
                    "Node type " + node.getType() + " not a recognized relational op node type");
        }

        boolean isAll = false;
        boolean isAny = false;
        if (node.getChild(1).getType() == ALL) {
            isAll = true;
        }
        if ((node.getChild(1).getType() == ANY) || (node.getChild(1).getType() == SOME)) {
            isAny = true;
        }

        ExprNode result;
        if (isAll || isAny) {
            if ((node.getChildCount() > 2) && (node.getChild(2).getType() == SUBSELECT_GROUP_EXPR)) {
                StatementSpecRaw currentSpec = popStacks();
                result = new ExprSubselectAllSomeAnyNode(currentSpec, false, isAll, relationalOpEnum);
            } else {
                result = new ExprRelationalOpAllAnyNode(relationalOpEnum, isAll);
            }
        } else {
            result = new ExprRelationalOpNodeImpl(relationalOpEnum);
        }

        astExprNodeMap.put(node, result);
    }

    private void leaveBitWise(Tree node) {
        log.debug(".leaveBitWise");

        BitWiseOpEnum bitWiseOpEnum;
        switch (node.getType()) {
        case BAND:
            bitWiseOpEnum = BitWiseOpEnum.BAND;
            break;
        case BOR:
            bitWiseOpEnum = BitWiseOpEnum.BOR;
            break;
        case BXOR:
            bitWiseOpEnum = BitWiseOpEnum.BXOR;
            break;
        default:
            throw new IllegalArgumentException(
                    "Node type " + node.getType() + " not a recognized bit wise node type");
        }

        ExprBitWiseNode bwNode = new ExprBitWiseNode(bitWiseOpEnum);
        astExprNodeMap.put(node, bwNode);
    }

    private void leaveWhereClause() {
        log.debug(".leaveWhereClause");

        if (astExprNodeMap.size() != 1) {
            throw new IllegalStateException("Where clause generated zero or more then one expression nodes");
        }

        // Just assign the single root ExprNode not consumed yet
        statementSpec.setFilterExprRootNode(astExprNodeMap.values().iterator().next());
        astExprNodeMap.clear();
    }

    private void leaveHavingClause() {
        log.debug(".leaveHavingClause");

        if (astExprNodeMap.size() != 1) {
            throw new IllegalStateException("Having clause generated zero or more then one expression nodes");
        }

        // Just assign the single root ExprNode not consumed yet
        statementSpec.setHavingExprRootNode(astExprNodeMap.values().iterator().next());
        astExprNodeMap.clear();
    }

    private void leaveOutputLimit(Tree node) throws ASTWalkException {
        log.debug(".leaveOutputLimit");

        OutputLimitSpec spec = ASTOutputLimitHelper.buildOutputLimitSpec(node, astExprNodeMap, variableService,
                engineURI, timeProvider, exprEvaluatorContext);
        statementSpec.setOutputLimitSpec(spec);

        if (spec.getVariableName() != null) {
            statementSpec.setHasVariables(true);
            addVariable(statementSpec, spec.getVariableName());
        }
    }

    private void leaveRowLimit(Tree node) throws ASTWalkException {
        log.debug(".leaveRowLimit");

        RowLimitSpec spec = ASTOutputLimitHelper.buildRowLimitSpec(node);
        statementSpec.setRowLimitSpec(spec);

        if ((spec.getNumRowsVariable() != null) || (spec.getOptionalOffsetVariable() != null)) {
            statementSpec.setHasVariables(true);
            addVariable(statementSpec, spec.getOptionalOffsetVariable());
        }
    }

    private void leaveOuterInnerJoin(Tree node) {
        log.debug(".leaveOuterInnerJoin");

        OuterJoinType joinType;
        switch (node.getType()) {
        case LEFT_OUTERJOIN_EXPR:
            joinType = OuterJoinType.LEFT;
            break;
        case RIGHT_OUTERJOIN_EXPR:
            joinType = OuterJoinType.RIGHT;
            break;
        case FULL_OUTERJOIN_EXPR:
            joinType = OuterJoinType.FULL;
            break;
        case INNERJOIN_EXPR:
            joinType = OuterJoinType.INNER;
            break;
        default:
            throw new IllegalArgumentException(
                    "Node type " + node.getType() + " not a recognized outer join node type");
        }

        // always starts with ON-token, so as to not produce an empty node
        ExprIdentNode left = null;
        ExprIdentNode right = null;
        ExprIdentNode[] addLeftArr = null;
        ExprIdentNode[] addRightArr = null;

        // get subnodes representing the on-expression, if provided
        if (node.getChildCount() > 1) {
            left = (ExprIdentNode) astExprNodeMap.get(node.getChild(1));
            right = (ExprIdentNode) astExprNodeMap.get(node.getChild(2));

            // remove from AST-to-expression node map
            astExprNodeMap.remove(node.getChild(1));
            astExprNodeMap.remove(node.getChild(2));

            // get optional additional
            if (node.getChildCount() > 3) {
                ArrayList<ExprIdentNode> addLeft = new ArrayList<ExprIdentNode>();
                ArrayList<ExprIdentNode> addRight = new ArrayList<ExprIdentNode>();
                for (int i = 3; i < node.getChildCount(); i += 2) {
                    Tree child = node.getChild(i);
                    addLeft.add((ExprIdentNode) astExprNodeMap.remove(child));
                    addRight.add((ExprIdentNode) astExprNodeMap.remove(node.getChild(i + 1)));
                }
                addLeftArr = addLeft.toArray(new ExprIdentNode[addLeft.size()]);
                addRightArr = addRight.toArray(new ExprIdentNode[addRight.size()]);
            }
        }

        OuterJoinDesc outerJoinDesc = new OuterJoinDesc(joinType, left, right, addLeftArr, addRightArr);
        statementSpec.getOuterJoinDescList().add(outerJoinDesc);
    }

    private void leaveGroupBy(Tree node) {
        log.debug(".leaveGroupBy");

        // there must be some expressions under the group by in our map
        if (astExprNodeMap.size() < 1) {
            throw new IllegalStateException("Group-by clause generated no expression nodes");
        }

        // For each child to the group-by AST node there must be a generated ExprNode
        for (int i = 0; i < node.getChildCount(); i++) {
            Tree child = node.getChild(i);
            // get top expression node for the child node
            ExprNode exprNode = astExprNodeMap.get(child);

            if (exprNode == null) {
                throw new IllegalStateException(
                        "Expression node as a result of group-by child node not found in collection");
            }

            statementSpec.getGroupByExpressions().add(exprNode);
        }

        // Clear the map - all expression node should be gone
        astExprNodeMap.clear();
    }

    private void leaveInsertInto(Tree node) {
        log.debug(".leaveInsertInto");

        int count = 0;
        Tree child = node.getChild(count);

        // istream or rstream
        SelectClauseStreamSelectorEnum selector = SelectClauseStreamSelectorEnum.ISTREAM_ONLY;
        if (child.getType() == RSTREAM) {
            selector = SelectClauseStreamSelectorEnum.RSTREAM_ONLY;
            child = node.getChild(++count);
        }
        if (child.getType() == IRSTREAM) {
            selector = SelectClauseStreamSelectorEnum.RSTREAM_ISTREAM_BOTH;
            child = node.getChild(++count);
        }
        if (child.getType() == ISTREAM) {
            child = node.getChild(++count);
        }

        // type name
        String eventTypeName = child.getText();
        InsertIntoDesc insertIntoDesc = new InsertIntoDesc(selector, eventTypeName);

        // optional columns
        child = node.getChild(++count);
        if ((child != null) && (child.getType() == EXPRCOL)) {
            // Each child to the insert-into AST node represents a column name
            for (int i = 0; i < child.getChildCount(); i++) {
                Tree childNode = child.getChild(i);
                insertIntoDesc.add(childNode.getText());
            }
        }

        statementSpec.setInsertIntoDesc(insertIntoDesc);
    }

    private void leaveOrderByElement(Tree node) throws ASTWalkException {
        log.debug(".leaveOrderByElement");
        if ((astExprNodeMap.size() > 1) || ((astExprNodeMap.isEmpty()))) {
            throw new ASTWalkException("Unexpected AST tree contains zero or more then 1 child element for root");
        }

        // Get expression node sub-tree from the AST nodes placed so far
        ExprNode exprNode = astExprNodeMap.values().iterator().next();
        astExprNodeMap.clear();

        // Get optional ascending or descending qualifier
        boolean descending = false;
        if (node.getChildCount() > 1) {
            descending = node.getChild(1).getType() == DESC;
        }

        // Add as order-by element
        statementSpec.getOrderByList().add(new OrderByItem(exprNode, descending));
    }

    private void leaveConcat(Tree node) {
        ExprConcatNode concatNode = new ExprConcatNode();
        astExprNodeMap.put(node, concatNode);
    }

    private void leaveEvery(Tree node) {
        log.debug(".leaveEvery");
        EvalFactoryNode everyNode = this.patternNodeFactory.makeEveryNode();
        addEvalNodeExpression(everyNode, node);
    }

    private void leaveEveryDistinct(Tree node) {
        log.debug(".leaveEveryDistinct");
        List<ExprNode> exprNodes = ASTExprHelper.getExprNodes(node.getChild(0), 0, astExprNodeMap);
        EvalFactoryNode everyNode = this.patternNodeFactory.makeEveryDistinctNode(exprNodes);
        addEvalNodeExpression(everyNode, node);
    }

    private void leaveStreamFilter(Tree node) {
        log.debug(".leaveStreamFilter");

        // for event streams we keep the filter spec around for use when the stream definition is completed
        filterSpec = ASTExprHelper.walkFilterSpec(node, propertyEvalSpec, astExprNodeMap);

        // set property eval to null
        propertyEvalSpec = null;

        // clear the sub-nodes for the filter since the event property expressions have been processed
        // by building the spec
        astExprNodeMap.clear();
    }

    private void leavePatternFilter(Tree node) {
        log.debug(".leavePatternFilter");

        int count = 0;
        Tree startNode = node.getChild(0);
        String optionalPatternTagName = null;
        if (startNode.getType() == IDENT) {
            optionalPatternTagName = startNode.getText();
            startNode = node.getChild(++count);
        }

        // Determine event type
        String eventName = startNode.getText();
        count++;

        // get property expression if any
        if ((node.getChildCount() > count) && (node.getChild(count).getType() == EVENT_FILTER_PROPERTY_EXPR)) {
            ++count;
        }

        Integer consumption = null;
        if ((node.getChildCount() > count) && (node.getChild(count).getType() == ATCHAR)) {
            Tree filterConsumeAnno = node.getChild(count);
            String name = filterConsumeAnno.getChild(0).getText();
            if (!name.toUpperCase().equals("CONSUME")) {
                throw new EPException(
                        "Unexpected pattern filter @ annotation, expecting 'consume' but received '" + name + "'");
            }
            Object number = filterConsumeAnno.getChildCount() < 2 ? null
                    : ASTConstantHelper.parse(filterConsumeAnno.getChild(1));
            if (number != null) {
                consumption = ((Number) number).intValue();
            } else {
                consumption = 1;
            }
            count++;
        }

        List<ExprNode> exprNodes = ASTExprHelper.getExprNodes(node, count, astExprNodeMap);

        FilterSpecRaw rawFilterSpec = new FilterSpecRaw(eventName, exprNodes, propertyEvalSpec);
        propertyEvalSpec = null;
        EvalFactoryNode filterNode = patternNodeFactory.makeFilterNode(rawFilterSpec, optionalPatternTagName,
                consumption);
        addEvalNodeExpression(filterNode, node);
    }

    private void leaveFollowedBy(Tree node) {
        log.debug(".leaveFollowedBy");
        ExprNode[] maxExpressions = new ExprNode[node.getChildCount() - 1];
        List<EvalFactoryNode> childNodes = new ArrayList<EvalFactoryNode>();
        for (int i = 0; i < node.getChildCount(); i++) {
            Tree child = node.getChild(i);
            if (child.getType() != FOLLOWED_BY_ITEM) {
                throw new ASTWalkException("Unexpected child node for followed-by item");
            }
            if (i == 0) {
                childNodes.add(astPatternNodeMap.remove(child.getChild(0))); // first pattern sub-expression cannot have max
            } else {
                int current = 0;
                if (child.getChildCount() == 2) {
                    maxExpressions[i - 1] = astExprNodeMap.remove(child.getChild(current));
                    current++;
                }
                childNodes.add(astPatternNodeMap.remove(child.getChild(current)));
            }
        }
        List<ExprNode> expressions = Collections.emptyList();
        if (!CollectionUtil.isAllNullArray(maxExpressions)) {
            expressions = Arrays.asList(maxExpressions); // can contain null elements as max/no-max can be mixed
        }
        EvalFactoryNode fbNode = patternNodeFactory.makeFollowedByNode(expressions,
                configurationInformation.getEngineDefaults().getPatterns().getMaxSubexpressions() != null);
        fbNode.addChildNodes(childNodes);
        addEvalNodeExpression(fbNode, node);
    }

    private void addEvalNodeExpression(EvalFactoryNode evalNode, Tree node) {
        astPatternNodeMap.put(node, evalNode);
        if (evalNodeExpressions == null) {
            evalNodeExpressions = new HashMap<EvalFactoryNode, String>();
        }
        evalNodeExpressions.put(evalNode, ASTExprHelper.getExpressionText(this.tokenStream, node));
    }

    private void leaveAnd(Tree node) {
        log.debug(".leaveAnd");
        EvalFactoryNode andNode = patternNodeFactory.makeAndNode();
        addEvalNodeExpression(andNode, node);
    }

    private void leaveOr(Tree node) {
        log.debug(".leaveOr");
        EvalFactoryNode orNode = patternNodeFactory.makeOrNode();
        addEvalNodeExpression(orNode, node);
    }

    private void leaveInSet(Tree node) {
        log.debug(".leaveInSet");

        ExprInNode inNode = new ExprInNodeImpl(node.getType() == NOT_IN_SET);
        astExprNodeMap.put(node, inNode);
    }

    private void leaveInRange(Tree node) {
        log.debug(".leaveInRange");

        // The second node must be braces
        Tree bracesNode = node.getChild(1);
        if ((bracesNode.getType() != LBRACK) && ((bracesNode.getType() != LPAREN))) {
            throw new IllegalStateException(
                    "Invalid in-range syntax, no braces but type '" + bracesNode.getType() + "'");
        }
        boolean isLowInclude = bracesNode.getType() == LBRACK;

        // The fifth node must be braces
        bracesNode = node.getChild(4);
        if ((bracesNode.getType() != RBRACK) && ((bracesNode.getType() != RPAREN))) {
            throw new IllegalStateException(
                    "Invalid in-range syntax, no braces but type '" + bracesNode.getType() + "'");
        }
        boolean isHighInclude = bracesNode.getType() == RBRACK;

        ExprBetweenNode betweenNode = new ExprBetweenNodeImpl(isLowInclude, isHighInclude,
                node.getType() == NOT_IN_RANGE);
        astExprNodeMap.put(node, betweenNode);
    }

    private void leaveBetween(Tree node) {
        log.debug(".leaveBetween");

        ExprBetweenNode betweenNode = new ExprBetweenNodeImpl(true, true, node.getType() == NOT_BETWEEN);
        astExprNodeMap.put(node, betweenNode);
    }

    private void leaveLike(Tree node) {
        log.debug(".leaveLike");

        boolean isNot = node.getType() == NOT_LIKE;
        ExprLikeNode likeNode = new ExprLikeNode(isNot);
        astExprNodeMap.put(node, likeNode);
    }

    private void leaveRegexp(Tree node) {
        log.debug(".leaveRegexp");

        boolean isNot = node.getType() == NOT_REGEXP;
        ExprRegexpNode regExpNode = new ExprRegexpNode(isNot);
        astExprNodeMap.put(node, regExpNode);
    }

    private void leaveExprNot(Tree node) {
        log.debug(".leaveExprNot");
        ExprNotNode notNode = new ExprNotNode();
        astExprNodeMap.put(node, notNode);
    }

    private void leavePatternNot(Tree node) {
        log.debug(".leavePatternNot");
        EvalFactoryNode notNode = this.patternNodeFactory.makeNotNode();
        addEvalNodeExpression(notNode, node);
    }

    private void leaveGuard(Tree node) throws ASTWalkException {
        log.debug(".leaveGuard");
        String objectNamespace;
        String objectName;
        List<ExprNode> obsParameters;
        if (node.getChild(1).getType() == IDENT && node.getChild(2).getType() == IDENT) {
            objectNamespace = node.getChild(1).getText();
            objectName = node.getChild(2).getText();
            obsParameters = ASTExprHelper.getExprNodes(node, 3, astExprNodeMap);
        } else {
            objectNamespace = GuardEnum.WHILE_GUARD.getNamespace();
            objectName = GuardEnum.WHILE_GUARD.getName();
            obsParameters = ASTExprHelper.getExprNodes(node, 1, astExprNodeMap);
        }

        PatternGuardSpec guardSpec = new PatternGuardSpec(objectNamespace, objectName, obsParameters);
        EvalFactoryNode guardNode = patternNodeFactory.makeGuardNode(guardSpec);
        addEvalNodeExpression(guardNode, node);
    }

    private void leaveCaseNode(Tree node, boolean inCase2) {
        if (log.isDebugEnabled()) {
            log.debug(".leaveCase2Node inCase2=" + inCase2);
        }

        if (astExprNodeMap.isEmpty()) {
            throw new ASTWalkException("Unexpected AST tree contains zero child element for case node");
        }
        if (astExprNodeMap.size() == 1) {
            throw new ASTWalkException("AST tree doesn not contain at least when node for case node");
        }

        ExprCaseNode caseNode = new ExprCaseNode(inCase2);
        astExprNodeMap.put(node, caseNode);
    }

    private void leaveExpressionDecl(Tree node) {
        if (log.isDebugEnabled()) {
            log.debug(".leaveExpressionEval");
        }
        Pair<ExpressionDeclItem, ExpressionScriptProvided> pair = ASTExpressionDeclHelper.walkExpressionDecl(node,
                scriptBodies, astExprNodeMap);
        if (pair.getFirst() != null) {
            expressionDeclarations.add(pair.getFirst());
        } else {
            scriptExpressions.add(pair.getSecond());
        }
    }

    private void leaveNewKeyword(Tree node) {
        if (log.isDebugEnabled()) {
            log.debug(".leaveNewKeyword");
        }

        List<String> columnNames = new ArrayList<String>();
        List<ExprNode> expressions = new ArrayList<ExprNode>();
        for (int i = 0; i < node.getChildCount(); i++) {
            Tree child = node.getChild(i);
            if (child.getType() != NEW_ITEM) {
                throw new IllegalStateException("Expected new-item node not found");
            }
            String property = ASTFilterSpecHelper.getPropertyName(child.getChild(0), 0);
            columnNames.add(property);

            ExprNode expr;
            if (child.getChildCount() > 1) {
                expr = astExprNodeMap.remove(child.getChild(1));
            } else {
                expr = new ExprIdentNodeImpl(property);
            }
            expressions.add(expr);
        }
        String[] columns = columnNames.toArray(new String[columnNames.size()]);
        ExprNewNode newNode = new ExprNewNode(columns);
        newNode.addChildNodes(expressions);
        astExprNodeMap.put(node, newNode);
    }

    private void leaveContext(Tree node) {
        if (log.isDebugEnabled()) {
            log.debug(".leaveContext");
        }
        String contextName = node.getChild(0).getText();
        statementSpec.setOptionalContextName(contextName);
        contextDescriptor = contextManagementService.getContextDescriptor(contextName);
    }

    private void leaveCreateContext(Tree node) {
        if (log.isDebugEnabled()) {
            log.debug(".leaveCreateContext");
        }

        CreateContextDesc contextDesc = ASTContextHelper.walkCreateContext(node, astExprNodeMap, astPatternNodeMap,
                propertyEvalSpec, filterSpec);
        filterSpec = null;
        propertyEvalSpec = null;
        statementSpec.setCreateContextDesc(contextDesc);
    }

    private void leaveCreateDataflow(Tree node) {
        if (log.isDebugEnabled()) {
            log.debug(".leaveCreateDataflow");
        }

        CreateDataFlowDesc graphDesc = ASTGraphHelper.walkCreateDataFlow(node, astGOPNodeMap, engineImportService);
        statementSpec.setCreateDataFlowDesc(graphDesc);
    }

    private void leaveGraphDetail(Tree node) {
        if (log.isDebugEnabled()) {
            log.debug(".leaveGraphDetail");
        }

        Object value;
        if (node.getType() == GOPCFGITM) {
            value = astExprNodeMap.remove(node.getChild(1));
        } else if (node.getType() == GOPCFGEXP) {
            value = astExprNodeMap.remove(node.getChild(0));
        } else {
            StatementSpecRaw newSpec = new StatementSpecRaw(defaultStreamSelector);
            newSpec.getAnnotations().addAll(statementSpec.getAnnotations());

            StatementSpecRaw existingSpec = statementSpec;
            value = existingSpec;
            existingSpec.setAnnotations(Collections.<AnnotationDesc>emptyList()); // clearing property-level annotations

            statementSpec = newSpec;
        }
        astGOPNodeMap.put(node, value);
    }

    private void leaveObserver(Tree node) throws ASTWalkException {
        log.debug(".leaveObserver");

        // Get the object information from AST
        String objectNamespace = node.getChild(0).getText();
        String objectName = node.getChild(1).getText();
        List<ExprNode> obsParameters = ASTExprHelper.getExprNodes(node, 2, astExprNodeMap);

        PatternObserverSpec observerSpec = new PatternObserverSpec(objectNamespace, objectName, obsParameters);
        EvalFactoryNode observerNode = this.patternNodeFactory.makeObserverNode(observerSpec);
        addEvalNodeExpression(observerNode, node);
    }

    private void leaveMatch(Tree node) throws ASTWalkException {
        log.debug(".leaveMatch");

        boolean hasRange = true;
        int type = node.getChild(0).getType();
        ExprNode low = null;
        ExprNode high = null;
        boolean allowZeroLowerBounds = false;
        if (type == MATCH_UNTIL_RANGE_HALFOPEN) // [expr:]
        {
            low = astExprNodeMap.remove(node.getChild(0).getChild(0));
        } else if (type == MATCH_UNTIL_RANGE_HALFCLOSED) // [:expr]
        {
            high = astExprNodeMap.remove(node.getChild(0).getChild(0));
        } else if (type == MATCH_UNTIL_RANGE_BOUNDED) // [expr]
        {
            low = astExprNodeMap.remove(node.getChild(0).getChild(0));
            high = low;
        } else if (type == MATCH_UNTIL_RANGE_CLOSED) // [expr:expr]
        {
            low = astExprNodeMap.remove(node.getChild(0).getChild(0));
            high = astExprNodeMap.remove(node.getChild(0).getChild(1));
            allowZeroLowerBounds = true;
        } else {
            hasRange = false;
        }

        boolean tightlyBound = ASTMatchUntilHelper.validate(low, high, allowZeroLowerBounds);
        if ((node.getChildCount() == 2) && (hasRange) && (!tightlyBound)) {
            throw new ASTWalkException("Variable bounds repeat operator requires an until-expression");
        }

        EvalFactoryNode fbNode = this.patternNodeFactory.makeMatchUntilNode(low, high);
        addEvalNodeExpression(fbNode, node);
    }

    private void leaveSelectClause(Tree node) {
        log.debug(".leaveSelectClause");

        int nodeType = node.getChild(0).getType();
        if (nodeType == RSTREAM) {
            statementSpec.setSelectStreamDirEnum(SelectClauseStreamSelectorEnum.RSTREAM_ONLY);
        }
        if (nodeType == ISTREAM) {
            statementSpec.setSelectStreamDirEnum(SelectClauseStreamSelectorEnum.ISTREAM_ONLY);
        }
        if (nodeType == IRSTREAM) {
            statementSpec.setSelectStreamDirEnum(SelectClauseStreamSelectorEnum.RSTREAM_ISTREAM_BOTH);
        }

        boolean isDistinct = false;
        for (int i = 0; i < node.getChildCount(); i++) {
            if (node.getChild(i).getType() == DISTINCT) {
                isDistinct = true;
            }
        }
        statementSpec.getSelectClauseSpec().setDistinct(isDistinct);
    }

    private ExprNode getRemoveFirstByType(Tree parent, int type) {
        ExprNode exprNode = null;
        for (int i = 0; i < parent.getChildCount(); i++) {
            if (parent.getChild(i).getType() == type) {
                exprNode = astExprNodeMap.get(parent.getChild(i).getChild(0));
                if (exprNode == null) {
                    throw new IllegalStateException("Expression node for AST node not found for type "
                            + parent.getChild(i).getType() + " and text " + parent.getChild(i).getText());
                }
                astExprNodeMap.remove(parent.getChild(i));
            }
        }
        return exprNode;
    }

    private boolean isSelectInsertFirst(Tree child) {
        for (int i = 0; i < child.getChildCount(); i++) {
            if (child.getChild(i).getType() == ON_SELECT_INSERT_OUTPUT) {
                if (child.getChild(i).getChild(0).getType() == ALL) {
                    return false;
                }
            }
        }
        return true;
    }

    private List<ExprChainedSpec> getLibFuncChain(Tree parent) {

        List<ExprChainedSpec> chained = new ArrayList<ExprChainedSpec>();
        for (int i = 0; i < parent.getChildCount(); i++) {
            Tree chainElement = parent.getChild(i);
            if (chainElement.getType() != LIB_FUNCTION) {
                continue;
            }

            ExprChainedSpec chainSpec = ASTLibHelper.getLibFunctionChainSpec(chainElement, astExprNodeMap);
            chained.add(chainSpec);
        }
        return chained;
    }

    private String getDefaultDialect() {
        return configurationInformation.getEngineDefaults().getScripts().getDefaultDialect();
    }

    private static final Log log = LogFactory.getLog(EPLTreeWalker.class);
}