com.espertech.esper.core.EPStatementStartMethod.java Source code

Java tutorial

Introduction

Here is the source code for com.espertech.esper.core.EPStatementStartMethod.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.core;

import com.espertech.esper.client.*;
import com.espertech.esper.client.annotation.HintEnum;
import com.espertech.esper.client.annotation.HookType;
import com.espertech.esper.client.hook.SQLColumnTypeConversion;
import com.espertech.esper.client.hook.SQLOutputRowConversion;
import com.espertech.esper.collection.Pair;
import com.espertech.esper.collection.UniformPair;
import com.espertech.esper.epl.agg.AggregationService;
import com.espertech.esper.epl.agg.AggregationServiceFactory;
import com.espertech.esper.epl.core.*;
import com.espertech.esper.epl.db.DatabasePollingViewableFactory;
import com.espertech.esper.epl.declexpr.ExprDeclaredNode;
import com.espertech.esper.epl.expression.*;
import com.espertech.esper.epl.join.base.*;
import com.espertech.esper.epl.join.plan.CoercionDesc;
import com.espertech.esper.epl.join.plan.CoercionUtil;
import com.espertech.esper.epl.join.plan.QueryPlanIndexBuilder;
import com.espertech.esper.epl.join.table.*;
import com.espertech.esper.epl.lookup.*;
import com.espertech.esper.epl.named.*;
import com.espertech.esper.epl.spec.*;
import com.espertech.esper.epl.subquery.SubqueryStopCallback;
import com.espertech.esper.epl.subquery.SubselectAggregatorView;
import com.espertech.esper.epl.subquery.SubselectBufferObserver;
import com.espertech.esper.epl.variable.CreateVariableView;
import com.espertech.esper.epl.variable.OnSetVariableView;
import com.espertech.esper.epl.variable.VariableDeclarationException;
import com.espertech.esper.epl.variable.VariableExistsException;
import com.espertech.esper.epl.view.FilterExprView;
import com.espertech.esper.epl.view.OutputConditionExpression;
import com.espertech.esper.epl.view.OutputProcessView;
import com.espertech.esper.epl.view.OutputProcessViewFactory;
import com.espertech.esper.epl.virtualdw.VirtualDWView;
import com.espertech.esper.epl.virtualdw.VirtualDWViewFactory;
import com.espertech.esper.event.EventAdapterException;
import com.espertech.esper.event.EventTypeMetadata;
import com.espertech.esper.event.EventTypeUtility;
import com.espertech.esper.event.map.MapEventType;
import com.espertech.esper.event.vaevent.ValueAddEventProcessor;
import com.espertech.esper.filter.FilterSpecCompiled;
import com.espertech.esper.pattern.*;
import com.espertech.esper.rowregex.EventRowRegexNFAViewFactory;
import com.espertech.esper.util.AuditPath;
import com.espertech.esper.util.JavaClassHelper;
import com.espertech.esper.util.StopCallback;
import com.espertech.esper.util.UuidGenerator;
import com.espertech.esper.view.*;
import com.espertech.esper.view.internal.BufferView;
import com.espertech.esper.view.internal.RouteResultView;
import com.espertech.esper.view.internal.SingleStreamDispatchView;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.naming.NamingException;
import java.lang.annotation.Annotation;
import java.util.*;

/**
 * Starts and provides the stop method for EPL statements.
 */
public class EPStatementStartMethod {
    private static final Log log = LogFactory.getLog(EPStatementStartMethod.class);
    private static final Log queryPlanLog = LogFactory.getLog(AuditPath.QUERYPLAN_LOG);

    private final StatementSpecCompiled statementSpec;
    private final EPServicesContext services;
    private final StatementContext statementContext;
    private final boolean queryPlanLogging;

    /**
     * Ctor.
     * @param statementSpec is a container for the definition of all statement constructs that
     * may have been used in the statement, i.e. if defines the select clauses, insert into, outer joins etc.
     * @param services is the service instances for dependency injection
     * @param statementContext is statement-level information and statement services
     */
    public EPStatementStartMethod(StatementSpecCompiled statementSpec, EPServicesContext services,
            StatementContext statementContext) {
        this.statementSpec = statementSpec;
        this.services = services;
        this.statementContext = statementContext;
        this.queryPlanLogging = services.getConfigSnapshot().getEngineDefaults().getLogging().isEnableQueryPlan();
        if (queryPlanLogging && queryPlanLog.isInfoEnabled()) {
            queryPlanLog.info("Query plans for statement '" + statementContext.getStatementName() + "' expression '"
                    + statementContext.getExpression() + "'");
        }
    }

    /**
     * Starts the EPL statement.
     * @return a viewable to attach to for listening to events, and a stop method to invoke to clean up
     * @param isNewStatement indicator whether the statement is new or a stop-restart statement
     * @param isRecoveringStatement true to indicate the statement is in the process of being recovered
     * @param isRecoveringResilient true to indicate the statement is in the process of being recovered and that statement is resilient
     * @throws ExprValidationException when the expression validation fails
     * @throws ViewProcessingException when views cannot be started
     */
    public EPStatementStartResult start(boolean isNewStatement, boolean isRecoveringStatement,
            boolean isRecoveringResilient) throws ExprValidationException, ViewProcessingException {
        statementContext.getVariableService().setLocalVersion(); // get current version of variables

        if (statementSpec.getUpdateSpec() != null) {
            return startUpdate();
        }
        if (statementSpec.getOnTriggerDesc() != null) {
            return startOnTrigger();
        } else if (statementSpec.getCreateWindowDesc() != null) {
            return startCreateWindow(isNewStatement, isRecoveringStatement);
        } else if (statementSpec.getCreateIndexDesc() != null) {
            return startCreateIndex();
        } else if (statementSpec.getCreateSchemaDesc() != null) {
            return startCreateSchema();
        } else if (statementSpec.getCreateVariableDesc() != null) {
            return startCreateVariable(isNewStatement);
        } else {
            return startSelect(isRecoveringResilient);
        }
    }

    private EPStatementStartResult startCreateIndex() throws ExprValidationException, ViewProcessingException {
        final CreateIndexDesc spec = statementSpec.getCreateIndexDesc();
        final NamedWindowProcessor processor = services.getNamedWindowService().getProcessor(spec.getWindowName());

        EPStatementStopMethod stopMethod;
        if (processor.isVirtualDataWindow()) {
            processor.getVirtualDataWindow().handleStartIndex(spec);
            stopMethod = new EPStatementStopMethod() {
                public void stop() {
                    processor.getVirtualDataWindow().handleStopIndex(spec);
                }
            };
        } else {
            processor.getRootView().addExplicitIndex(spec.getWindowName(), spec.getIndexName(), spec.getColumns());
            stopMethod = new EPStatementStopMethod() {
                public void stop() {
                    processor.getRootView().removeExplicitIndex(spec.getIndexName());
                }
            };
        }

        Viewable viewable = new ViewableDefaultImpl(processor.getNamedWindowType());
        return new EPStatementStartResult(viewable, stopMethod, null);
    }

    private EPStatementStartResult startCreateSchema() throws ExprValidationException {
        final CreateSchemaDesc spec = statementSpec.getCreateSchemaDesc();
        EventType eventType = null;

        try {
            if (!spec.isVariant()) {
                if (spec.getTypes().isEmpty()) {
                    Map<String, Object> typing = TypeBuilderUtil.buildType(spec.getColumns());
                    ConfigurationEventTypeMap config = new ConfigurationEventTypeMap();
                    if (spec.getInherits() != null) {
                        config.getSuperTypes().addAll(spec.getInherits());
                    }
                    config.setStartTimestampPropertyName(spec.getStartTimestampProperty());
                    config.setEndTimestampPropertyName(spec.getEndTimestampProperty());
                    eventType = services.getEventAdapterService().addNestableMapType(spec.getSchemaName(), typing,
                            config, false, false, true, false, false);
                } else {
                    if (spec.getTypes().size() == 1) {
                        String typeName = spec.getTypes().iterator().next();
                        try {
                            // use the existing configuration, if any, possibly adding the start and end timestamps
                            ConfigurationEventTypeLegacy config = services.getEventAdapterService()
                                    .getClassLegacyConfigs(typeName);
                            if (spec.getStartTimestampProperty() != null
                                    || spec.getEndTimestampProperty() != null) {
                                if (config == null) {
                                    config = new ConfigurationEventTypeLegacy();
                                }
                                config.setStartTimestampPropertyName(spec.getStartTimestampProperty());
                                config.setEndTimestampPropertyName(spec.getEndTimestampProperty());
                                services.getEventAdapterService()
                                        .setClassLegacyConfigs(Collections.singletonMap(typeName, config));
                            }
                            eventType = services.getEventAdapterService().addBeanType(spec.getSchemaName(),
                                    spec.getTypes().iterator().next(), false, false, false, true);
                        } catch (EventAdapterException ex) {
                            Class clazz;
                            try {
                                clazz = services.getEngineImportService().resolveClass(typeName);
                                eventType = services.getEventAdapterService().addBeanType(spec.getSchemaName(),
                                        clazz, false, false, true);
                            } catch (EngineImportException e) {
                                log.debug("Engine import failed to resolve event type '" + typeName + "'");
                                throw ex;
                            }
                        }
                    }
                }
            } else {
                boolean isAny = false;
                ConfigurationVariantStream config = new ConfigurationVariantStream();
                for (String typeName : spec.getTypes()) {
                    if (typeName.trim().equals("*")) {
                        isAny = true;
                        break;
                    }
                    config.addEventTypeName(typeName);
                }
                if (!isAny) {
                    config.setTypeVariance(ConfigurationVariantStream.TypeVariance.PREDEFINED);
                } else {
                    config.setTypeVariance(ConfigurationVariantStream.TypeVariance.ANY);
                }
                services.getValueAddEventService().addVariantStream(spec.getSchemaName(), config,
                        services.getEventAdapterService(), services.getEventTypeIdGenerator());
                eventType = services.getValueAddEventService().getValueAddProcessor(spec.getSchemaName())
                        .getValueAddEventType();
            }
        } catch (RuntimeException ex) {
            throw new ExprValidationException(ex.getMessage(), ex);
        }

        // enter a reference
        services.getStatementEventTypeRefService().addReferences(statementContext.getStatementName(),
                Collections.singleton(spec.getSchemaName()));

        final EventType allocatedEventType = eventType;
        EPStatementStopMethod stopMethod = new EPStatementStopMethod() {
            public void stop() {
                services.getStatementEventTypeRefService()
                        .removeReferencesStatement(statementContext.getStatementName());
                if (services.getStatementEventTypeRefService().getStatementNamesForType(spec.getSchemaName())
                        .isEmpty()) {
                    services.getEventAdapterService().removeType(allocatedEventType.getName());
                    services.getFilterService().removeType(allocatedEventType);
                }
            }
        };
        Viewable viewable = new ViewableDefaultImpl(eventType);
        EPStatementStartResult result = new EPStatementStartResult(viewable, stopMethod, null);
        return result;
    }

    private EPStatementStartResult startOnTrigger() throws ExprValidationException, ViewProcessingException {
        final List<StopCallback> stopCallbacks = new LinkedList<StopCallback>();

        SubSelectStreamCollection subSelectStreamDesc = createSubSelectStreams(true,
                statementSpec.getAnnotations());

        // Create streams
        Viewable eventStreamParentViewable;
        final StreamSpecCompiled streamSpec = statementSpec.getStreamSpecs().get(0);
        String triggereventTypeName = null;

        if (streamSpec instanceof FilterStreamSpecCompiled) {
            FilterStreamSpecCompiled filterStreamSpec = (FilterStreamSpecCompiled) streamSpec;
            triggereventTypeName = filterStreamSpec.getFilterSpec().getFilterForEventTypeName();

            // Since only for non-joins we get the existing stream's lock and try to reuse it's views
            boolean filterSubselectSameStream = determineSubquerySameStream(filterStreamSpec);
            Pair<EventStream, StatementLock> streamLockPair = services.getStreamService().createStream(
                    statementContext.getStatementId(), filterStreamSpec.getFilterSpec(),
                    statementContext.getFilterService(), statementContext.getEpStatementHandle(), false, false,
                    statementContext, true, filterSubselectSameStream, statementContext.getAnnotations());
            eventStreamParentViewable = streamLockPair.getFirst();

            // Use the re-used stream's lock for all this statement's locking needs
            if (streamLockPair.getSecond() != null) {
                statementContext.getEpStatementHandle().setStatementLock(streamLockPair.getSecond());
            }
        } else if (streamSpec instanceof PatternStreamSpecCompiled) {
            PatternStreamSpecCompiled patternStreamSpec = (PatternStreamSpecCompiled) streamSpec;
            boolean usedByChildViews = !streamSpec.getViewSpecs().isEmpty()
                    || (statementSpec.getInsertIntoDesc() != null);
            String patternTypeName = statementContext.getStatementId() + "_patternon";
            final EventType eventType = services.getEventAdapterService().createSemiAnonymousMapType(
                    patternTypeName, patternStreamSpec.getTaggedEventTypes(),
                    patternStreamSpec.getArrayEventTypes(), usedByChildViews);
            final EventStream sourceEventStream = new ZeroDepthStream(eventType);
            eventStreamParentViewable = sourceEventStream;

            EvalRootNode rootNode = services.getPatternNodeFactory().makeRootNode();
            rootNode.addChildNode(patternStreamSpec.getEvalNode());

            PatternMatchCallback callback = new PatternMatchCallback() {
                public void matchFound(Map<String, Object> matchEvent) {
                    EventBean compositeEvent = statementContext.getEventAdapterService()
                            .adaptorForTypedMap(matchEvent, eventType);
                    sourceEventStream.insert(compositeEvent);
                }
            };

            PatternContext patternContext = statementContext.getPatternContextFactory().createContext(
                    statementContext, 0, rootNode, !patternStreamSpec.getArrayEventTypes().isEmpty(),
                    isConsumingFilters(patternStreamSpec.getEvalNode()));
            PatternStopCallback patternStopCallback = rootNode.start(callback, patternContext);
            stopCallbacks.add(patternStopCallback);
        } else if (streamSpec instanceof NamedWindowConsumerStreamSpec) {
            NamedWindowConsumerStreamSpec namedSpec = (NamedWindowConsumerStreamSpec) streamSpec;
            NamedWindowProcessor processor = services.getNamedWindowService()
                    .getProcessor(namedSpec.getWindowName());
            eventStreamParentViewable = processor.addConsumer(namedSpec.getFilterExpressions(),
                    namedSpec.getOptPropertyEvaluator(), statementContext.getEpStatementHandle(),
                    statementContext.getStatementStopService());
            triggereventTypeName = namedSpec.getWindowName();
        } else {
            throw new ExprValidationException("Unknown stream specification type: " + streamSpec);
        }

        // create stop method using statement stream specs
        EPStatementStopMethod stopMethod = new EPStatementStopMethod() {
            public void stop() {
                statementContext.getStatementStopService().fireStatementStopped();

                if (streamSpec instanceof FilterStreamSpecCompiled) {
                    FilterStreamSpecCompiled filterStreamSpec = (FilterStreamSpecCompiled) streamSpec;
                    boolean filterSubselectSameStream = determineSubquerySameStream(filterStreamSpec);
                    services.getStreamService().dropStream(filterStreamSpec.getFilterSpec(),
                            statementContext.getFilterService(), false, false, true, filterSubselectSameStream);
                }
                for (StopCallback stopCallback : stopCallbacks) {
                    stopCallback.stop();
                }
            }
        };

        View onExprView;
        try {
            final EventType streamEventType = eventStreamParentViewable.getEventType();

            ResultSetProcessor resultSetProcessor;
            // For on-delete and on-select and on-update triggers
            if (statementSpec.getOnTriggerDesc() instanceof OnTriggerWindowDesc) {
                // Determine event types
                OnTriggerWindowDesc onTriggerDesc = (OnTriggerWindowDesc) statementSpec.getOnTriggerDesc();
                NamedWindowProcessor processor = services.getNamedWindowService()
                        .getProcessor(onTriggerDesc.getWindowName());
                EventType namedWindowType = processor.getNamedWindowType();
                statementContext.getDynamicReferenceEventTypes().add(onTriggerDesc.getWindowName());

                String namedWindowName = onTriggerDesc.getOptionalAsName();
                if (namedWindowName == null) {
                    namedWindowName = "stream_0";
                }
                String streamName = streamSpec.getOptionalStreamName();
                if (streamName == null) {
                    streamName = "stream_1";
                }
                String namedWindowTypeName = onTriggerDesc.getWindowName();

                // Materialize sub-select views
                // 0 - named window stream
                // 1 - arriving stream
                startSubSelect(subSelectStreamDesc,
                        new String[] { namedWindowName, streamSpec.getOptionalStreamName() },
                        new EventType[] { processor.getNamedWindowType(), streamEventType },
                        new String[] { namedWindowTypeName, triggereventTypeName }, stopCallbacks,
                        statementSpec.getAnnotations(), statementSpec.getDeclaredExpressions());

                StreamTypeService typeService = new StreamTypeServiceImpl(
                        new EventType[] { namedWindowType, streamEventType },
                        new String[] { namedWindowName, streamName }, new boolean[] { false, true },
                        services.getEngineURI(), false);
                if (onTriggerDesc instanceof OnTriggerWindowUpdateDesc) {
                    OnTriggerWindowUpdateDesc updateDesc = (OnTriggerWindowUpdateDesc) onTriggerDesc;
                    ExprValidationContext validationContext = new ExprValidationContext(typeService,
                            statementContext.getMethodResolutionService(), null,
                            statementContext.getSchedulingService(), statementContext.getVariableService(),
                            statementContext, statementContext.getEventAdapterService(),
                            statementContext.getStatementName(), statementContext.getStatementId(),
                            statementContext.getAnnotations());
                    for (OnTriggerSetAssignment assignment : updateDesc.getAssignments()) {
                        ExprNode validated = ExprNodeUtility.getValidatedSubtree(assignment.getExpression(),
                                validationContext);
                        assignment.setExpression(validated);
                        validateNoAggregations(validated,
                                "Aggregation functions may not be used within an on-update-clause");
                    }
                }
                if (onTriggerDesc instanceof OnTriggerMergeDesc) {
                    OnTriggerMergeDesc mergeDesc = (OnTriggerMergeDesc) onTriggerDesc;
                    validateMergeDesc(mergeDesc, statementContext, processor.getNamedWindowType(), namedWindowName,
                            streamEventType, streamName);
                }

                // validate join expression
                ExprNode validatedJoin = validateJoinNamedWindow(statementSpec.getFilterRootNode(), namedWindowType,
                        namedWindowName, namedWindowTypeName, streamEventType, streamName, triggereventTypeName);

                // validate filter, output rate limiting
                validateNodes(statementSpec, statementContext, typeService, null);

                // Construct a processor for results; for use in on-select to process selection results
                // Use a wildcard select if the select-clause is empty, such as for on-delete.
                // For on-select the select clause is not empty.
                if (statementSpec.getSelectClauseSpec().getSelectExprList().size() == 0) {
                    statementSpec.getSelectClauseSpec().add(new SelectClauseElementWildcard());
                }
                resultSetProcessor = ResultSetProcessorFactory.getProcessor(statementSpec, statementContext,
                        typeService, null, new boolean[0], true);

                InternalEventRouter routerService = null;
                boolean addToFront = false;
                if (statementSpec.getInsertIntoDesc() != null || onTriggerDesc instanceof OnTriggerMergeDesc) {
                    routerService = services.getInternalEventRouter();
                }
                if (statementSpec.getInsertIntoDesc() != null) {
                    addToFront = statementContext.getNamedWindowService()
                            .isNamedWindow(statementSpec.getInsertIntoDesc().getEventTypeName());
                }
                onExprView = processor.addOnExpr(onTriggerDesc, validatedJoin, streamEventType,
                        streamSpec.getOptionalStreamName(), statementContext.getStatementStopService(),
                        routerService, addToFront, resultSetProcessor, statementContext.getEpStatementHandle(),
                        statementContext.getStatementResultService(), statementContext,
                        statementSpec.getSelectClauseSpec().isDistinct());
                eventStreamParentViewable.addView(onExprView);
            }
            // variable assignments
            else if (statementSpec.getOnTriggerDesc() instanceof OnTriggerSetDesc) {
                OnTriggerSetDesc desc = (OnTriggerSetDesc) statementSpec.getOnTriggerDesc();
                StreamTypeService typeService = new StreamTypeServiceImpl(new EventType[] { streamEventType },
                        new String[] { streamSpec.getOptionalStreamName() }, new boolean[] { true },
                        services.getEngineURI(), false);
                ExprValidationContext validationContext = new ExprValidationContext(typeService,
                        statementContext.getMethodResolutionService(), null,
                        statementContext.getSchedulingService(), statementContext.getVariableService(),
                        statementContext, statementContext.getEventAdapterService(),
                        statementContext.getStatementName(), statementContext.getStatementId(),
                        statementContext.getAnnotations());

                // Materialize sub-select views
                startSubSelect(subSelectStreamDesc, new String[] { streamSpec.getOptionalStreamName() },
                        new EventType[] { streamEventType }, new String[] { triggereventTypeName }, stopCallbacks,
                        statementSpec.getAnnotations(), statementSpec.getDeclaredExpressions());

                for (OnTriggerSetAssignment assignment : desc.getAssignments()) {
                    ExprNode validated = ExprNodeUtility.getValidatedSubtree(assignment.getExpression(),
                            validationContext);
                    assignment.setExpression(validated);
                }

                try {
                    onExprView = new OnSetVariableView(statementContext.getStatementId(), desc,
                            statementContext.getEventAdapterService(), statementContext.getVariableService(),
                            statementContext.getStatementResultService(), statementContext);
                } catch (VariableValueException ex) {
                    throw new ExprValidationException("Error in variable assignment: " + ex.getMessage(), ex);
                }
                eventStreamParentViewable.addView(onExprView);
            }
            // split-stream use case
            else {
                OnTriggerSplitStreamDesc desc = (OnTriggerSplitStreamDesc) statementSpec.getOnTriggerDesc();
                String streamName = streamSpec.getOptionalStreamName();
                if (streamName == null) {
                    streamName = "stream_0";
                }
                StreamTypeService typeService = new StreamTypeServiceImpl(new EventType[] { streamEventType },
                        new String[] { streamName }, new boolean[] { true }, services.getEngineURI(), false);
                if (statementSpec.getInsertIntoDesc() == null) {
                    throw new ExprValidationException(
                            "Required insert-into clause is not provided, the clause is required for split-stream syntax");
                }
                if ((!statementSpec.getGroupByExpressions().isEmpty())
                        || (statementSpec.getHavingExprRootNode() != null)
                        || (!statementSpec.getOrderByList().isEmpty())) {
                    throw new ExprValidationException(
                            "A group-by clause, having-clause or order-by clause is not allowed for the split stream syntax");
                }

                // Materialize sub-select views
                startSubSelect(subSelectStreamDesc, new String[] { streamSpec.getOptionalStreamName() },
                        new EventType[] { streamEventType }, new String[] { triggereventTypeName }, stopCallbacks,
                        statementSpec.getAnnotations(), statementSpec.getDeclaredExpressions());

                validateNodes(statementSpec, statementContext, typeService, null);

                ResultSetProcessor[] processors = new ResultSetProcessor[desc.getSplitStreams().size() + 1];
                ExprNode[] whereClauses = new ExprNode[desc.getSplitStreams().size() + 1];
                processors[0] = ResultSetProcessorFactory.getProcessor(statementSpec, statementContext, typeService,
                        null, new boolean[0], false);
                whereClauses[0] = statementSpec.getFilterRootNode();
                boolean[] isNamedWindowInsert = new boolean[desc.getSplitStreams().size() + 1];
                isNamedWindowInsert[0] = false;

                int index = 1;
                for (OnTriggerSplitStream splits : desc.getSplitStreams()) {
                    StatementSpecCompiled splitSpec = new StatementSpecCompiled();
                    splitSpec.setInsertIntoDesc(splits.getInsertInto());
                    splitSpec.setSelectClauseSpec(
                            StatementLifecycleSvcImpl.compileSelectAllowSubselect(splits.getSelectClause()));
                    splitSpec.setFilterExprRootNode(splits.getWhereClause());
                    validateNodes(splitSpec, statementContext, typeService, null);

                    processors[index] = ResultSetProcessorFactory.getProcessor(splitSpec, statementContext,
                            typeService, null, new boolean[0], false);
                    whereClauses[index] = splitSpec.getFilterRootNode();
                    isNamedWindowInsert[index] = statementContext.getNamedWindowService()
                            .isNamedWindow(splits.getInsertInto().getEventTypeName());

                    index++;
                }

                onExprView = new RouteResultView(desc.isFirst(), streamEventType,
                        statementContext.getEpStatementHandle(), services.getInternalEventRouter(),
                        isNamedWindowInsert, processors, whereClauses, statementContext);
                eventStreamParentViewable.addView(onExprView);
            }

            // For on-delete, create an output processor that passes on as a wildcard the underlying event
            if ((statementSpec.getOnTriggerDesc().getOnTriggerType() == OnTriggerType.ON_DELETE)
                    || (statementSpec.getOnTriggerDesc().getOnTriggerType() == OnTriggerType.ON_SET)
                    || (statementSpec.getOnTriggerDesc().getOnTriggerType() == OnTriggerType.ON_UPDATE)
                    || (statementSpec.getOnTriggerDesc().getOnTriggerType() == OnTriggerType.ON_MERGE)) {
                StatementSpecCompiled defaultSelectAllSpec = new StatementSpecCompiled();
                defaultSelectAllSpec.getSelectClauseSpec().add(new SelectClauseElementWildcard());

                StreamTypeService streamTypeService = new StreamTypeServiceImpl(
                        new EventType[] { onExprView.getEventType() }, new String[] { "trigger_stream" },
                        new boolean[] { true }, services.getEngineURI(), false);
                ResultSetProcessor outputResultSetProcessor = ResultSetProcessorFactory.getProcessor(
                        defaultSelectAllSpec, statementContext, streamTypeService, null, new boolean[0], true);

                // Attach output view
                OutputProcessView outputView = OutputProcessViewFactory.makeView(outputResultSetProcessor,
                        defaultSelectAllSpec, statementContext, services.getInternalEventRouter());
                onExprView.addView(outputView);
                onExprView = outputView;
            }
        } catch (ExprValidationException ex) {
            handleException(stopMethod);
            throw ex;
        } catch (RuntimeException ex) {
            handleException(stopMethod);
            throw ex;
        }
        log.debug(".start Statement start completed");

        return new EPStatementStartResult(onExprView, stopMethod);
    }

    private ExprNode validateExprNoAgg(ExprNode exprNode, StreamTypeService streamTypeService,
            StatementContext statementContext, String errorMsg) throws ExprValidationException {
        ExprValidationContext validationContext = new ExprValidationContext(streamTypeService,
                statementContext.getMethodResolutionService(), null, statementContext.getSchedulingService(),
                statementContext.getVariableService(), statementContext, statementContext.getEventAdapterService(),
                statementContext.getStatementName(), statementContext.getStatementId(),
                statementContext.getAnnotations());
        ExprNode validated = ExprNodeUtility.getValidatedSubtree(exprNode, validationContext);
        validateNoAggregations(validated, errorMsg);
        return validated;
    }

    private void validateMergeDesc(OnTriggerMergeDesc mergeDesc, StatementContext statementContext,
            EventType namedWindowType, String namedWindowName, EventType triggerStreamType,
            String triggerStreamName) throws ExprValidationException {
        String exprNodeErrorMessage = "Aggregation functions may not be used within an merge-clause";
        for (OnTriggerMergeMatched matchedItem : mergeDesc.getItems()) {

            EventType dummyTypeNoProperties = new MapEventType(
                    EventTypeMetadata.createAnonymous("merge_named_window_insert"), "merge_named_window_insert", 0,
                    null, Collections.<String, Object>emptyMap(), null, null, null);
            StreamTypeService twoStreamTypeSvc = new StreamTypeServiceImpl(
                    new EventType[] { namedWindowType, triggerStreamType },
                    new String[] { namedWindowName, triggerStreamName }, new boolean[] { true, true },
                    statementContext.getEngineURI(), false);
            StreamTypeService insertOnlyTypeSvc = new StreamTypeServiceImpl(
                    new EventType[] { dummyTypeNoProperties, triggerStreamType },
                    new String[] { UuidGenerator.generate(), triggerStreamName }, new boolean[] { true, true },
                    statementContext.getEngineURI(), false);

            if (matchedItem.getOptionalMatchCond() != null) {
                StreamTypeService matchValidStreams = matchedItem.isMatchedUnmatched() ? twoStreamTypeSvc
                        : insertOnlyTypeSvc;
                matchedItem.setOptionalMatchCond(validateExprNoAgg(matchedItem.getOptionalMatchCond(),
                        matchValidStreams, statementContext, exprNodeErrorMessage));
                if (!matchedItem.isMatchedUnmatched()) {
                    validateSubqueryExcludeOuterStream(matchedItem.getOptionalMatchCond());
                }
            }

            for (OnTriggerMergeAction item : matchedItem.getActions()) {
                if (item instanceof OnTriggerMergeActionDelete) {
                    OnTriggerMergeActionDelete delete = (OnTriggerMergeActionDelete) item;
                    if (delete.getOptionalWhereClause() != null) {
                        delete.setOptionalWhereClause(validateExprNoAgg(delete.getOptionalWhereClause(),
                                twoStreamTypeSvc, statementContext, exprNodeErrorMessage));
                    }
                } else if (item instanceof OnTriggerMergeActionUpdate) {
                    OnTriggerMergeActionUpdate update = (OnTriggerMergeActionUpdate) item;
                    if (update.getOptionalWhereClause() != null) {
                        update.setOptionalWhereClause(validateExprNoAgg(update.getOptionalWhereClause(),
                                twoStreamTypeSvc, statementContext, exprNodeErrorMessage));
                    }
                    for (OnTriggerSetAssignment assignment : update.getAssignments()) {
                        assignment.setExpression(validateExprNoAgg(assignment.getExpression(), twoStreamTypeSvc,
                                statementContext, exprNodeErrorMessage));
                    }
                } else if (item instanceof OnTriggerMergeActionInsert) {
                    OnTriggerMergeActionInsert insert = (OnTriggerMergeActionInsert) item;

                    StreamTypeService insertTypeSvc;
                    if (insert.getOptionalStreamName() == null
                            || insert.getOptionalStreamName().equals(namedWindowName)) {
                        insertTypeSvc = insertOnlyTypeSvc;
                    } else {
                        insertTypeSvc = twoStreamTypeSvc;
                    }

                    List<SelectClauseElementCompiled> compiledSelect = new ArrayList<SelectClauseElementCompiled>();
                    if (insert.getOptionalWhereClause() != null) {
                        insert.setOptionalWhereClause(validateExprNoAgg(insert.getOptionalWhereClause(),
                                insertTypeSvc, statementContext, exprNodeErrorMessage));
                    }
                    int colIndex = 0;
                    for (SelectClauseElementRaw raw : insert.getSelectClause()) {
                        if (raw instanceof SelectClauseStreamRawSpec) {
                            SelectClauseStreamRawSpec rawStreamSpec = (SelectClauseStreamRawSpec) raw;
                            Integer foundStreamNum = null;
                            for (int s = 0; s < insertTypeSvc.getStreamNames().length; s++) {
                                if (rawStreamSpec.getStreamName().equals(insertTypeSvc.getStreamNames()[s])) {
                                    foundStreamNum = s;
                                    break;
                                }
                            }
                            if (foundStreamNum == null) {
                                throw new ExprValidationException(
                                        "Stream by name '" + rawStreamSpec.getStreamName() + "' was not found");
                            }
                            SelectClauseStreamCompiledSpec streamSelectSpec = new SelectClauseStreamCompiledSpec(
                                    rawStreamSpec.getStreamName(), rawStreamSpec.getOptionalAsName());
                            streamSelectSpec.setStreamNumber(foundStreamNum);
                            compiledSelect.add(streamSelectSpec);
                        } else if (raw instanceof SelectClauseExprRawSpec) {
                            SelectClauseExprRawSpec exprSpec = (SelectClauseExprRawSpec) raw;
                            ExprValidationContext validationContext = new ExprValidationContext(insertTypeSvc,
                                    statementContext.getMethodResolutionService(), null,
                                    statementContext.getTimeProvider(), statementContext.getVariableService(),
                                    statementContext, statementContext.getEventAdapterService(),
                                    statementContext.getStatementName(), statementContext.getStatementId(),
                                    statementContext.getAnnotations());
                            ExprNode exprCompiled = ExprNodeUtility
                                    .getValidatedSubtree(exprSpec.getSelectExpression(), validationContext);
                            String resultName = exprSpec.getOptionalAsName();
                            if (resultName == null) {
                                if (insert.getColumns().size() > colIndex) {
                                    resultName = insert.getColumns().get(colIndex);
                                } else {
                                    resultName = exprCompiled.toExpressionString();
                                }
                            }
                            compiledSelect.add(new SelectClauseExprCompiledSpec(exprCompiled, resultName,
                                    exprSpec.getOptionalAsName()));
                            validateNoAggregations(exprCompiled,
                                    "Expression in a merge-selection may not utilize aggregation functions");
                        } else if (raw instanceof SelectClauseElementWildcard) {
                            compiledSelect.add(new SelectClauseElementWildcard());
                        } else {
                            throw new IllegalStateException("Unknown select clause item:" + raw);
                        }
                        colIndex++;
                    }
                    insert.setSelectClauseCompiled(compiledSelect);
                } else {
                    throw new IllegalArgumentException(
                            "Unrecognized merge item '" + item.getClass().getName() + "'");
                }
            }
        }
    }

    // Special-case validation: When an on-merge query in the not-matched clause uses a subquery then
    // that subquery should not reference any of the stream's properties which are not-matched
    private void validateSubqueryExcludeOuterStream(ExprNode matchCondition) throws ExprValidationException {
        ExprNodeSubselectVisitor visitorSubselects = new ExprNodeSubselectVisitor();
        matchCondition.accept(visitorSubselects);
        if (visitorSubselects.getSubselects().isEmpty()) {
            return;
        }
        ExprNodeIdentifierCollectVisitor visitorProps = new ExprNodeIdentifierCollectVisitor();
        for (ExprSubselectNode node : visitorSubselects.getSubselects()) {
            if (node.getStatementSpecCompiled().getFilterRootNode() != null) {
                node.getStatementSpecCompiled().getFilterRootNode().accept(visitorProps);
            }
        }
        for (ExprIdentNode node : visitorProps.getExprProperties()) {
            if (node.getStreamId() == 1) {
                throw new ExprValidationException(
                        "On-Merge not-matched filter expression may not use properties that are provided by the named window event");
            }
        }
    }

    private EPStatementStartResult startUpdate() throws ExprValidationException, ViewProcessingException {
        final List<StopCallback> stopCallbacks = new LinkedList<StopCallback>();

        // First we create streams for subselects, if there are any
        SubSelectStreamCollection subSelectStreamDesc = createSubSelectStreams(false,
                statementSpec.getAnnotations());

        final StreamSpecCompiled streamSpec = statementSpec.getStreamSpecs().get(0);
        final UpdateDesc desc = statementSpec.getUpdateSpec();
        String triggereventTypeName;

        if (streamSpec instanceof FilterStreamSpecCompiled) {
            FilterStreamSpecCompiled filterStreamSpec = (FilterStreamSpecCompiled) streamSpec;
            triggereventTypeName = filterStreamSpec.getFilterSpec().getFilterForEventTypeName();
        } else if (streamSpec instanceof NamedWindowConsumerStreamSpec) {
            NamedWindowConsumerStreamSpec namedSpec = (NamedWindowConsumerStreamSpec) streamSpec;
            triggereventTypeName = namedSpec.getWindowName();
        } else {
            throw new ExprValidationException("Unknown stream specification streamEventType: " + streamSpec);
        }

        // determine a stream name
        String streamName = triggereventTypeName;
        if (desc.getOptionalStreamName() != null) {
            streamName = desc.getOptionalStreamName();
        }

        final EventType streamEventType = services.getEventAdapterService()
                .getExistsTypeByName(triggereventTypeName);
        StreamTypeService typeService = new StreamTypeServiceImpl(new EventType[] { streamEventType },
                new String[] { streamName }, new boolean[] { true }, services.getEngineURI(), false);

        // determine subscriber result types
        statementContext.getStatementResultService().setSelectClause(
                new Class[] { streamEventType.getUnderlyingType() }, new String[] { "*" }, false, null,
                statementContext);

        // Materialize sub-select views
        startSubSelect(subSelectStreamDesc, new String[] { streamName }, new EventType[] { streamEventType },
                new String[] { triggereventTypeName }, stopCallbacks, statementSpec.getAnnotations(),
                statementSpec.getDeclaredExpressions());

        ExprValidationContext validationContext = new ExprValidationContext(typeService,
                statementContext.getMethodResolutionService(), null, statementContext.getSchedulingService(),
                statementContext.getVariableService(), statementContext, statementContext.getEventAdapterService(),
                statementContext.getStatementName(), statementContext.getStatementId(),
                statementContext.getAnnotations());
        for (OnTriggerSetAssignment assignment : desc.getAssignments()) {
            ExprNode validated = ExprNodeUtility.getValidatedSubtree(assignment.getExpression(), validationContext);
            assignment.setExpression(validated);
            validateNoAggregations(validated, "Aggregation functions may not be used within an update-clause");
        }
        if (desc.getOptionalWhereClause() != null) {
            ExprNode validated = ExprNodeUtility.getValidatedSubtree(desc.getOptionalWhereClause(),
                    validationContext);
            desc.setOptionalWhereClause(validated);
            validateNoAggregations(validated, "Aggregation functions may not be used within an update-clause");
        }

        InternalRoutePreprocessView onExprView = new InternalRoutePreprocessView(streamEventType,
                statementContext.getStatementResultService());
        services.getInternalEventRouter().addPreprocessing(streamEventType, desc, statementSpec.getAnnotations(),
                onExprView);
        stopCallbacks.add(new StopCallback() {
            public void stop() {
                services.getInternalEventRouter().removePreprocessing(streamEventType, desc);
            }
        });

        EPStatementStopMethod stopMethod = new EPStatementStopMethod() {
            public void stop() {
                statementContext.getStatementStopService().fireStatementStopped();

                for (StopCallback stopCallback : stopCallbacks) {
                    stopCallback.stop();
                }
            }
        };
        return new EPStatementStartResult(onExprView, stopMethod);
    }

    private EPStatementStartResult startCreateWindow(boolean isNewStatement, boolean isRecoveringStatement)
            throws ExprValidationException, ViewProcessingException {
        final FilterStreamSpecCompiled filterStreamSpec = (FilterStreamSpecCompiled) statementSpec.getStreamSpecs()
                .get(0);
        String windowName = statementSpec.getCreateWindowDesc().getWindowName();
        EventType windowType = filterStreamSpec.getFilterSpec().getFilterForEventType();

        // Create streams and views
        Viewable eventStreamParentViewable;
        ViewFactoryChain unmaterializedViewChain;

        // Create view factories and parent view based on a filter specification
        // Since only for non-joins we get the existing stream's lock and try to reuse it's views
        boolean filterSubselectSameStream = determineSubquerySameStream(filterStreamSpec);
        Pair<EventStream, StatementLock> streamLockPair = services.getStreamService().createStream(
                statementContext.getStatementId(), filterStreamSpec.getFilterSpec(),
                statementContext.getFilterService(), statementContext.getEpStatementHandle(), false, false,
                statementContext, true, filterSubselectSameStream, statementContext.getAnnotations());
        eventStreamParentViewable = streamLockPair.getFirst();

        // Use the re-used stream's lock for all this statement's locking needs
        if (streamLockPair.getSecond() != null) {
            statementContext.getEpStatementHandle().setStatementLock(streamLockPair.getSecond());
        }

        // Create data window view factories
        unmaterializedViewChain = services.getViewService().createFactories(0,
                eventStreamParentViewable.getEventType(), filterStreamSpec.getViewSpecs(),
                filterStreamSpec.getOptions(), statementContext);

        ValueAddEventProcessor optionalRevisionProcessor = statementContext.getValueAddEventService()
                .getValueAddProcessor(windowName);
        boolean isPrioritized = services.getEngineSettingsService().getEngineSettings().getExecution()
                .isPrioritized();
        boolean isEnableSubqueryIndexShare = HintEnum.ENABLE_WINDOW_SUBQUERY_INDEXSHARE
                .getHint(statementSpec.getAnnotations()) != null;
        if (!isEnableSubqueryIndexShare
                && unmaterializedViewChain.getViewFactoryChain().get(0) instanceof VirtualDWViewFactory) {
            isEnableSubqueryIndexShare = true; // index share is always enabled for virtual data window (otherwise it wouldn't make sense)
        }
        services.getNamedWindowService().addProcessor(windowName, windowType,
                statementContext.getEpStatementHandle(), statementContext.getStatementResultService(),
                optionalRevisionProcessor, statementContext.getExpression(), statementContext.getStatementName(),
                isPrioritized, statementContext, isEnableSubqueryIndexShare);

        // The root view of the named window
        NamedWindowProcessor processor = services.getNamedWindowService()
                .getProcessor(statementSpec.getCreateWindowDesc().getWindowName());
        View rootView = processor.getRootView();
        eventStreamParentViewable.addView(rootView);

        // request remove stream capability from views
        ViewResourceDelegate viewResourceDelegate = new ViewResourceDelegateImpl(
                new ViewFactoryChain[] { unmaterializedViewChain }, statementContext);
        if (!viewResourceDelegate.requestCapability(0, new RemoveStreamViewCapability(false), null)) {
            throw new ExprValidationException(NamedWindowService.ERROR_MSG_DATAWINDOWS);
        }

        // Materialize views
        Viewable finalView = services.getViewService().createViews(rootView,
                unmaterializedViewChain.getViewFactoryChain(), statementContext);

        // If this is a virtual data window implementation, bind it to the context for easy lookup
        StopCallback envStopCallback = null;
        if (finalView instanceof VirtualDWView) {
            final String objectName = "/virtualdw/" + windowName;
            final VirtualDWView virtualDWView = (VirtualDWView) finalView;
            try {
                services.getEngineEnvContext().bind(objectName, virtualDWView.getVirtualDataWindow());
            } catch (NamingException e) {
                throw new ViewProcessingException("Invalid name for adding to context:" + e.getMessage(), e);
            }
            envStopCallback = new StopCallback() {
                public void stop() {
                    try {
                        virtualDWView.destroy();
                        services.getEngineEnvContext().unbind(objectName);
                    } catch (NamingException e) {
                    }
                }
            };
        }
        final StopCallback environmentStopCallback = envStopCallback;

        // create stop method using statement stream specs
        EPStatementStopMethod stopMethod = new EPStatementStopMethod() {
            public void stop() {
                statementContext.getStatementStopService().fireStatementStopped();
                boolean filterSubselectSameStream = determineSubquerySameStream(filterStreamSpec);
                services.getStreamService().dropStream(filterStreamSpec.getFilterSpec(),
                        statementContext.getFilterService(), false, false, true, filterSubselectSameStream);
                String windowName = statementSpec.getCreateWindowDesc().getWindowName();
                try {
                    NamedWindowProcessor processor = services.getNamedWindowService().getProcessor(windowName);
                    if (processor.isVirtualDataWindow()) {
                        processor.getVirtualDataWindow().handleStopWindow();
                    }
                } catch (ExprValidationException e) {
                    log.warn("Named window processor by name '" + windowName + "' has not been found");
                }
                services.getNamedWindowService().removeProcessor(windowName);
                if (environmentStopCallback != null) {
                    environmentStopCallback.stop();
                }
            }
        };

        // Attach tail view
        boolean isBatchView = finalView instanceof BatchingDataWindowView;
        NamedWindowTailView tailView = processor.getTailView();
        tailView.setBatchView(isBatchView);
        processor.getRootView().setBatchView(isBatchView);
        finalView.addView(tailView);
        finalView = tailView;

        // Add a wildcard to the select clause as subscribers received the window contents
        statementSpec.getSelectClauseSpec().getSelectExprList().clear();
        statementSpec.getSelectClauseSpec().add(new SelectClauseElementWildcard());
        statementSpec.setSelectStreamDirEnum(SelectClauseStreamSelectorEnum.RSTREAM_ISTREAM_BOTH);

        StreamTypeService typeService = new StreamTypeServiceImpl(new EventType[] { windowType },
                new String[] { windowName }, new boolean[] { true }, services.getEngineURI(), false);
        ResultSetProcessor resultSetProcessor = ResultSetProcessorFactory.getProcessor(statementSpec,
                statementContext, typeService, null, new boolean[0], true);

        // Attach output view
        OutputProcessView outputView = OutputProcessViewFactory.makeView(resultSetProcessor, statementSpec,
                statementContext, services.getInternalEventRouter());
        finalView.addView(outputView);
        finalView = outputView;

        // Handle insert case
        if (statementSpec.getCreateWindowDesc().isInsert() && !isRecoveringStatement) {
            String insertFromWindow = statementSpec.getCreateWindowDesc().getInsertFromWindow();
            NamedWindowProcessor sourceWindow = services.getNamedWindowService().getProcessor(insertFromWindow);
            List<EventBean> events = new ArrayList<EventBean>();
            if (statementSpec.getCreateWindowDesc().getInsertFilter() != null) {
                EventBean[] eventsPerStream = new EventBean[1];
                ExprEvaluator filter = statementSpec.getCreateWindowDesc().getInsertFilter().getExprEvaluator();
                for (Iterator<EventBean> it = sourceWindow.getTailView().iterator(); it.hasNext();) {
                    EventBean candidate = it.next();
                    eventsPerStream[0] = candidate;
                    Boolean result = (Boolean) filter.evaluate(eventsPerStream, true, statementContext);
                    if ((result == null) || (!result)) {
                        continue;
                    }
                    events.add(candidate);
                }
            } else {
                for (Iterator<EventBean> it = sourceWindow.getTailView().iterator(); it.hasNext();) {
                    events.add(it.next());
                }
            }
            if (events.size() > 0) {
                EventType rootViewType = rootView.getEventType();
                EventBean[] convertedEvents = services.getEventAdapterService().typeCast(events, rootViewType);
                rootView.update(convertedEvents, null);
            }
        }

        log.debug(".start Statement start completed");

        return new EPStatementStartResult(finalView, stopMethod);
    }

    private EPStatementStartResult startCreateVariable(boolean isNewStatement)
            throws ExprValidationException, ViewProcessingException {
        final CreateVariableDesc createDesc = statementSpec.getCreateVariableDesc();

        // Get assignment value
        Object value = null;
        if (createDesc.getAssignment() != null) {
            // Evaluate assignment expression
            StreamTypeService typeService = new StreamTypeServiceImpl(new EventType[0], new String[0],
                    new boolean[0], services.getEngineURI(), false);
            ExprValidationContext validationContext = new ExprValidationContext(typeService,
                    statementContext.getMethodResolutionService(), null, statementContext.getSchedulingService(),
                    statementContext.getVariableService(), statementContext,
                    statementContext.getEventAdapterService(), statementContext.getStatementName(),
                    statementContext.getStatementId(), statementContext.getAnnotations());
            ExprNode validated = ExprNodeUtility.getValidatedSubtree(createDesc.getAssignment(), validationContext);
            value = validated.getExprEvaluator().evaluate(null, true, statementContext);
        }

        // Create variable
        try {
            services.getVariableService().createNewVariable(createDesc.getVariableName(),
                    createDesc.getVariableType(), value, statementContext.getExtensionServicesContext());
        } catch (VariableExistsException ex) {
            // for new statement we don't allow creating the same variable
            if (isNewStatement) {
                throw new ExprValidationException("Cannot create variable: " + ex.getMessage());
            }
        } catch (VariableDeclarationException ex) {
            throw new ExprValidationException("Cannot create variable: " + ex.getMessage());
        }

        final CreateVariableView createView = new CreateVariableView(statementContext.getStatementId(),
                services.getEventAdapterService(), services.getVariableService(), createDesc.getVariableName(),
                statementContext.getStatementResultService());
        final int variableNum = services.getVariableService().getReader(createDesc.getVariableName())
                .getVariableNumber();
        services.getVariableService().registerCallback(variableNum, createView);
        statementContext.getStatementStopService().addSubscriber(new StatementStopCallback() {
            public void statementStopped() {
                services.getVariableService().unregisterCallback(variableNum, createView);
            }
        });

        // Create result set processor, use wildcard selection
        statementSpec.getSelectClauseSpec().getSelectExprList().clear();
        statementSpec.getSelectClauseSpec().add(new SelectClauseElementWildcard());
        statementSpec.setSelectStreamDirEnum(SelectClauseStreamSelectorEnum.RSTREAM_ISTREAM_BOTH);
        StreamTypeService typeService = new StreamTypeServiceImpl(new EventType[] { createView.getEventType() },
                new String[] { "create_variable" }, new boolean[] { true }, services.getEngineURI(), false);
        ResultSetProcessor resultSetProcessor = ResultSetProcessorFactory.getProcessor(statementSpec,
                statementContext, typeService, null, new boolean[0], true);

        // Attach output view
        OutputProcessView outputView = OutputProcessViewFactory.makeView(resultSetProcessor, statementSpec,
                statementContext, services.getInternalEventRouter());
        createView.addView(outputView);

        services.getStatementVariableRefService().addReferences(statementContext.getStatementName(),
                Collections.singleton(createDesc.getVariableName()));
        EPStatementDestroyMethod destroyMethod = new EPStatementDestroyMethod() {
            public void destroy() {
                try {
                    services.getStatementVariableRefService()
                            .removeReferencesStatement(statementContext.getStatementName());
                } catch (RuntimeException ex) {
                    log.error("Error removing variable '" + createDesc.getVariableName() + "': " + ex.getMessage());
                }
            }
        };

        EPStatementStopMethod stopMethod = new EPStatementStopMethod() {
            public void stop() {
            }
        };

        return new EPStatementStartResult(outputView, stopMethod, destroyMethod);
    }

    private EPStatementStartResult startSelect(boolean isRecoveringResilient)
            throws ExprValidationException, ViewProcessingException {
        // Determine stream names for each stream - some streams may not have a name given
        String[] streamNames = determineStreamNames(statementSpec.getStreamSpecs());
        final boolean isJoin = statementSpec.getStreamSpecs().size() > 1;

        // First we create streams for subselects, if there are any
        SubSelectStreamCollection subSelectStreamDesc = createSubSelectStreams(isJoin,
                statementSpec.getAnnotations());

        int numStreams = streamNames.length;
        final List<StopCallback> stopCallbacks = new LinkedList<StopCallback>();

        // Create streams and views
        Viewable[] eventStreamParentViewable = new Viewable[numStreams];
        ViewFactoryChain[] unmaterializedViewChain = new ViewFactoryChain[numStreams];
        String[] eventTypeNamees = new String[numStreams];
        boolean[] isNamedWindow = new boolean[numStreams];

        // verify for joins that required views are present
        StreamJoinAnalysisResult joinAnalysisResult = verifyJoinViews(statementSpec.getStreamSpecs(),
                statementContext.getNamedWindowService());

        for (int i = 0; i < statementSpec.getStreamSpecs().size(); i++) {
            StreamSpecCompiled streamSpec = statementSpec.getStreamSpecs().get(i);

            // Create view factories and parent view based on a filter specification
            if (streamSpec instanceof FilterStreamSpecCompiled) {
                FilterStreamSpecCompiled filterStreamSpec = (FilterStreamSpecCompiled) streamSpec;
                eventTypeNamees[i] = filterStreamSpec.getFilterSpec().getFilterForEventTypeName();

                // Since only for non-joins we get the existing stream's lock and try to reuse it's views
                boolean filterSubselectSameStream = determineSubquerySameStream(filterStreamSpec);
                Pair<EventStream, StatementLock> streamLockPair = services.getStreamService().createStream(
                        statementContext.getStatementId(), filterStreamSpec.getFilterSpec(),
                        statementContext.getFilterService(), statementContext.getEpStatementHandle(), isJoin, false,
                        statementContext, false | !statementSpec.getOrderByList().isEmpty(),
                        filterSubselectSameStream, statementContext.getAnnotations());
                eventStreamParentViewable[i] = streamLockPair.getFirst();

                // Use the re-used stream's lock for all this statement's locking needs
                if (streamLockPair.getSecond() != null) {
                    statementContext.getEpStatementHandle().setStatementLock(streamLockPair.getSecond());
                }

                unmaterializedViewChain[i] = services.getViewService().createFactories(i,
                        eventStreamParentViewable[i].getEventType(), streamSpec.getViewSpecs(),
                        streamSpec.getOptions(), statementContext);
            }
            // Create view factories and parent view based on a pattern expression
            else if (streamSpec instanceof PatternStreamSpecCompiled) {
                PatternStreamSpecCompiled patternStreamSpec = (PatternStreamSpecCompiled) streamSpec;
                boolean usedByChildViews = !streamSpec.getViewSpecs().isEmpty()
                        || (statementSpec.getInsertIntoDesc() != null);
                String patternTypeName = statementContext.getStatementId() + "_pattern_" + i;
                final EventType eventType = services.getEventAdapterService().createSemiAnonymousMapType(
                        patternTypeName, patternStreamSpec.getTaggedEventTypes(),
                        patternStreamSpec.getArrayEventTypes(), usedByChildViews);
                final EventStream sourceEventStream = new ZeroDepthStream(eventType);
                eventStreamParentViewable[i] = sourceEventStream;
                unmaterializedViewChain[i] = services.getViewService().createFactories(i,
                        sourceEventStream.getEventType(), streamSpec.getViewSpecs(), streamSpec.getOptions(),
                        statementContext);

                EvalRootNode rootNode = services.getPatternNodeFactory().makeRootNode();
                rootNode.addChildNode(patternStreamSpec.getEvalNode());

                PatternMatchCallback callback = new PatternMatchCallback() {
                    public void matchFound(Map<String, Object> matchEvent) {
                        EventBean compositeEvent = statementContext.getEventAdapterService()
                                .adaptorForTypedMap(matchEvent, eventType);
                        sourceEventStream.insert(compositeEvent);
                    }
                };

                PatternContext patternContext = statementContext.getPatternContextFactory().createContext(
                        statementContext, i, rootNode, !patternStreamSpec.getArrayEventTypes().isEmpty(),
                        isConsumingFilters(patternStreamSpec.getEvalNode()));
                PatternStopCallback patternStopCallback = rootNode.start(callback, patternContext);
                stopCallbacks.add(patternStopCallback);
            }
            // Create view factories and parent view based on a database SQL statement
            else if (streamSpec instanceof DBStatementStreamSpec) {
                if (!streamSpec.getViewSpecs().isEmpty()) {
                    throw new ExprValidationException(
                            "Historical data joins do not allow views onto the data, view '"
                                    + streamSpec.getViewSpecs().get(0).getObjectNamespace() + ':'
                                    + streamSpec.getViewSpecs().get(0).getObjectName()
                                    + "' is not valid in this context");
                }

                DBStatementStreamSpec sqlStreamSpec = (DBStatementStreamSpec) streamSpec;
                SQLColumnTypeConversion typeConversionHook = (SQLColumnTypeConversion) JavaClassHelper
                        .getAnnotationHook(statementSpec.getAnnotations(), HookType.SQLCOL,
                                SQLColumnTypeConversion.class, statementContext.getMethodResolutionService());
                SQLOutputRowConversion outputRowConversionHook = (SQLOutputRowConversion) JavaClassHelper
                        .getAnnotationHook(statementSpec.getAnnotations(), HookType.SQLROW,
                                SQLOutputRowConversion.class, statementContext.getMethodResolutionService());
                HistoricalEventViewable historicalEventViewable = DatabasePollingViewableFactory
                        .createDBStatementView(statementContext.getStatementId(), i, sqlStreamSpec,
                                services.getDatabaseRefService(), services.getEventAdapterService(),
                                statementContext.getEpStatementHandle(), typeConversionHook,
                                outputRowConversionHook, statementContext.getConfigSnapshot().getEngineDefaults()
                                        .getLogging().isEnableJDBC());
                unmaterializedViewChain[i] = new ViewFactoryChain(historicalEventViewable.getEventType(),
                        new LinkedList<ViewFactory>());
                eventStreamParentViewable[i] = historicalEventViewable;
                stopCallbacks.add(historicalEventViewable);
            } else if (streamSpec instanceof MethodStreamSpec) {
                if (!streamSpec.getViewSpecs().isEmpty()) {
                    throw new ExprValidationException("Method data joins do not allow views onto the data, view '"
                            + streamSpec.getViewSpecs().get(0).getObjectNamespace() + ':'
                            + streamSpec.getViewSpecs().get(0).getObjectName() + "' is not valid in this context");
                }

                MethodStreamSpec methodStreamSpec = (MethodStreamSpec) streamSpec;
                HistoricalEventViewable historicalEventViewable = MethodPollingViewableFactory.createPollMethodView(
                        i, methodStreamSpec, services.getEventAdapterService(),
                        statementContext.getEpStatementHandle(), statementContext.getMethodResolutionService(),
                        services.getEngineImportService(), statementContext.getSchedulingService(),
                        statementContext.getScheduleBucket(), statementContext);
                unmaterializedViewChain[i] = new ViewFactoryChain(historicalEventViewable.getEventType(),
                        new LinkedList<ViewFactory>());
                eventStreamParentViewable[i] = historicalEventViewable;
                stopCallbacks.add(historicalEventViewable);
            } else if (streamSpec instanceof NamedWindowConsumerStreamSpec) {
                NamedWindowConsumerStreamSpec namedSpec = (NamedWindowConsumerStreamSpec) streamSpec;
                NamedWindowProcessor processor = services.getNamedWindowService()
                        .getProcessor(namedSpec.getWindowName());
                NamedWindowConsumerView consumerView = processor.addConsumer(namedSpec.getFilterExpressions(),
                        namedSpec.getOptPropertyEvaluator(), statementContext.getEpStatementHandle(),
                        statementContext.getStatementStopService());
                eventStreamParentViewable[i] = consumerView;
                unmaterializedViewChain[i] = services.getViewService().createFactories(i,
                        consumerView.getEventType(), namedSpec.getViewSpecs(), namedSpec.getOptions(),
                        statementContext);
                joinAnalysisResult.setNamedWindow(i);
                eventTypeNamees[i] = namedSpec.getWindowName();
                isNamedWindow[i] = true;

                // Consumers to named windows cannot declare a data window view onto the named window to avoid duplicate remove streams
                ViewResourceDelegate viewResourceDelegate = new ViewResourceDelegateImpl(unmaterializedViewChain,
                        statementContext);
                viewResourceDelegate.requestCapability(i, new NotADataWindowViewCapability(), null);
            } else {
                throw new ExprValidationException("Unknown stream specification type: " + streamSpec);
            }
        }

        if (statementSpec.getMatchRecognizeSpec() != null) {
            if (isJoin) {
                throw new ExprValidationException("Joins are not allowed when using match recognize");
            }
            boolean isUnbound = (unmaterializedViewChain[0].getViewFactoryChain().isEmpty())
                    && (!(statementSpec.getStreamSpecs().get(0) instanceof NamedWindowConsumerStreamSpec));
            EventRowRegexNFAViewFactory factory = new EventRowRegexNFAViewFactory(unmaterializedViewChain[0],
                    statementSpec.getMatchRecognizeSpec(), statementContext, isUnbound,
                    statementSpec.getAnnotations());
            unmaterializedViewChain[0].getViewFactoryChain().add(factory);
        }

        // Obtain event types from ViewFactoryChains
        EventType[] streamEventTypes = new EventType[statementSpec.getStreamSpecs().size()];
        for (int i = 0; i < unmaterializedViewChain.length; i++) {
            streamEventTypes[i] = unmaterializedViewChain[i].getEventType();
        }

        // Materialize sub-select views
        startSubSelect(subSelectStreamDesc, streamNames, streamEventTypes, eventTypeNamees, stopCallbacks,
                statementSpec.getAnnotations(), statementSpec.getDeclaredExpressions());

        // List of statement streams
        final List<StreamSpecCompiled> statementStreamSpecs = new ArrayList<StreamSpecCompiled>();
        statementStreamSpecs.addAll(statementSpec.getStreamSpecs());

        // Construct type information per stream
        StreamTypeService typeService = new StreamTypeServiceImpl(streamEventTypes, streamNames,
                getHasIStreamOnly(isNamedWindow, unmaterializedViewChain), services.getEngineURI(), false);
        ViewResourceDelegate viewResourceDelegate = new ViewResourceDelegateImpl(unmaterializedViewChain,
                statementContext);

        // boolean multiple expiry policy
        for (int i = 0; i < unmaterializedViewChain.length; i++) {
            if (unmaterializedViewChain[i].getDataWindowViewFactoryCount() > 1) {
                if (!viewResourceDelegate.requestCapability(i, new RemoveStreamViewCapability(true), null)) {
                    log.warn(
                            "Combination of multiple data window expiry policies with views that do not support remove streams is not allowed");
                }
            }
        }

        // create stop method using statement stream specs
        EPStatementStopMethod stopMethod = new EPStatementStopMethod() {
            public void stop() {
                statementContext.getStatementStopService().fireStatementStopped();

                for (StreamSpecCompiled streamSpec : statementStreamSpecs) {
                    if (streamSpec instanceof FilterStreamSpecCompiled) {
                        FilterStreamSpecCompiled filterStreamSpec = (FilterStreamSpecCompiled) streamSpec;
                        boolean filterSubselectSameStream = determineSubquerySameStream(filterStreamSpec);
                        services.getStreamService().dropStream(filterStreamSpec.getFilterSpec(),
                                statementContext.getFilterService(), isJoin, false,
                                false | !statementSpec.getOrderByList().isEmpty(), filterSubselectSameStream);
                    }
                }
                for (StopCallback stopCallback : stopCallbacks) {
                    stopCallback.stop();
                }
                for (ExprSubselectNode subselect : statementSpec.getSubSelectExpressions()) {
                    StreamSpecCompiled subqueryStreamSpec = subselect.getStatementSpecCompiled().getStreamSpecs()
                            .get(0);
                    if (subqueryStreamSpec instanceof FilterStreamSpecCompiled) {
                        FilterStreamSpecCompiled filterStreamSpec = (FilterStreamSpecCompiled) subselect
                                .getStatementSpecCompiled().getStreamSpecs().get(0);
                        services.getStreamService().dropStream(filterStreamSpec.getFilterSpec(),
                                statementContext.getFilterService(), isJoin, true, false, false);
                    }
                }
            }
        };

        Viewable finalView;

        try {

            // Validate views that require validation, specifically streams that don't have
            // sub-views such as DB SQL joins
            for (int stream = 0; stream < eventStreamParentViewable.length; stream++) {
                Viewable viewable = eventStreamParentViewable[stream];
                if (viewable instanceof ValidatedView) {
                    ValidatedView validatedView = (ValidatedView) viewable;
                    validatedView.validate(services.getEngineImportService(), typeService,
                            statementContext.getMethodResolutionService(), statementContext.getTimeProvider(),
                            statementContext.getVariableService(), statementContext, services.getConfigSnapshot(),
                            services.getSchedulingService(), services.getEngineURI(),
                            statementSpec.getSqlParameters(), statementContext.getEventAdapterService(),
                            statementContext.getStatementName(), statementContext.getStatementId(),
                            statementContext.getAnnotations());
                }
                if (viewable instanceof HistoricalEventViewable) {
                    HistoricalEventViewable historicalView = (HistoricalEventViewable) viewable;
                    if (historicalView.getRequiredStreams().contains(stream)) {
                        throw new ExprValidationException("Parameters for historical stream " + stream
                                + " indicate that the stream is subordinate to itself as stream parameters originate in the same stream");
                    }
                }
            }

            // Construct a processor for results posted by views and joins, which takes care of aggregation if required.
            // May return null if we don't need to post-process results posted by views or joins.
            ResultSetProcessor resultSetProcessor = ResultSetProcessorFactory.getProcessor(statementSpec,
                    statementContext, typeService, viewResourceDelegate, joinAnalysisResult.getUnidirectionalInd(),
                    true);

            // Validate where-clause filter tree, outer join clause and output limit expression
            validateNodes(statementSpec, statementContext, typeService, viewResourceDelegate);

            // Materialize views
            Viewable[] streamViews = new Viewable[streamEventTypes.length];
            for (int i = 0; i < streamViews.length; i++) {
                streamViews[i] = services.getViewService().createViews(eventStreamParentViewable[i],
                        unmaterializedViewChain[i].getViewFactoryChain(), statementContext);
            }

            // For just 1 event stream without joins, handle the one-table process separatly.
            JoinPreloadMethod joinPreloadMethod = null;
            if (streamNames.length == 1) {
                finalView = handleSimpleSelect(streamViews[0], resultSetProcessor, statementContext);
            } else {
                Pair<Viewable, JoinPreloadMethod> pair = handleJoin(streamNames, streamEventTypes, streamViews,
                        resultSetProcessor, statementSpec.getSelectStreamSelectorEnum(), statementContext,
                        stopCallbacks, joinAnalysisResult);
                finalView = pair.getFirst();
                joinPreloadMethod = pair.getSecond();
            }

            // Replay any named window data, for later consumers of named data windows
            boolean hasNamedWindow = false;
            for (int i = 0; i < statementSpec.getStreamSpecs().size(); i++) {
                StreamSpecCompiled streamSpec = statementSpec.getStreamSpecs().get(i);
                if (streamSpec instanceof NamedWindowConsumerStreamSpec) {
                    hasNamedWindow = true;
                    NamedWindowConsumerStreamSpec namedSpec = (NamedWindowConsumerStreamSpec) streamSpec;
                    NamedWindowProcessor processor = services.getNamedWindowService()
                            .getProcessor(namedSpec.getWindowName());
                    NamedWindowTailView consumerView = processor.getTailView();
                    NamedWindowConsumerView view = (NamedWindowConsumerView) eventStreamParentViewable[i];

                    // preload view for stream unless the expiry policy is batch window
                    ArrayList<EventBean> eventsInWindow = new ArrayList<EventBean>();
                    if (!consumerView.isParentBatchWindow()) {
                        for (EventBean aConsumerView : consumerView) {
                            eventsInWindow.add(aConsumerView);
                        }
                    }
                    if (!eventsInWindow.isEmpty() && !isRecoveringResilient) {
                        EventBean[] newEvents = eventsInWindow.toArray(new EventBean[eventsInWindow.size()]);
                        view.update(newEvents, null);
                        if (joinPreloadMethod != null && !joinPreloadMethod.isPreloading()
                                && statementContext.getEpStatementHandle().getOptionalDispatchable() != null) {
                            statementContext.getEpStatementHandle().getOptionalDispatchable()
                                    .execute(statementContext);
                        }
                    }

                    // in a join, preload indexes, if any
                    if (joinPreloadMethod != null) {
                        joinPreloadMethod.preloadFromBuffer(i);
                    } else {
                        if (statementContext.getEpStatementHandle().getOptionalDispatchable() != null) {
                            statementContext.getEpStatementHandle().getOptionalDispatchable()
                                    .execute(statementContext);
                        }
                    }
                }
            }
            // last, for aggregation we need to send the current join results to the result set processor
            if ((hasNamedWindow) && (joinPreloadMethod != null) && (!isRecoveringResilient)
                    && resultSetProcessor.hasAggregation()) {
                joinPreloadMethod.preloadAggregation(resultSetProcessor);
            }
        } catch (ExprValidationException ex) {
            handleException(stopMethod);
            throw ex;
        } catch (RuntimeException ex) {
            handleException(stopMethod);
            throw ex;
        }

        log.debug(".start Statement start completed");

        return new EPStatementStartResult(finalView, stopMethod);
    }

    private boolean isConsumingFilters(EvalNode evalNode) {
        if (evalNode instanceof EvalFilterNode) {
            return ((EvalFilterNode) evalNode).getConsumptionLevel() != null;
        }
        boolean consumption = false;
        for (EvalNode child : evalNode.getChildNodes()) {
            consumption = consumption || isConsumingFilters(child);
        }
        return consumption;
    }

    private void handleException(EPStatementStopMethod stopMethod) {
        try {
            stopMethod.stop();
        } catch (RuntimeException e) {
            log.debug("Failed to perform statement stop for statement '" + this.statementContext.getStatementName()
                    + "' expression '" + statementContext.getExpression() + "' : " + e.getMessage());
        }
    }

    private boolean determineSubquerySameStream(FilterStreamSpecCompiled filterStreamSpec) {
        for (ExprSubselectNode subselect : statementSpec.getSubSelectExpressions()) {
            StreamSpecCompiled streamSpec = subselect.getStatementSpecCompiled().getStreamSpecs().get(0);
            if (!(streamSpec instanceof FilterStreamSpecCompiled)) {
                continue;
            }
            FilterStreamSpecCompiled filterStream = (FilterStreamSpecCompiled) streamSpec;
            EventType typeSubselect = filterStream.getFilterSpec().getFilterForEventType();
            EventType typeFiltered = filterStreamSpec.getFilterSpec().getFilterForEventType();
            if (EventTypeUtility.isTypeOrSubTypeOf(typeSubselect, typeFiltered)
                    || EventTypeUtility.isTypeOrSubTypeOf(typeFiltered, typeSubselect)) {
                return true;
            }
        }
        return false;
    }

    private boolean[] getHasIStreamOnly(boolean[] isNamedWindow, ViewFactoryChain[] unmaterializedViewChain) {
        boolean[] result = new boolean[unmaterializedViewChain.length];
        for (int i = 0; i < unmaterializedViewChain.length; i++) {
            if (isNamedWindow[i]) {
                continue;
            }
            result[i] = unmaterializedViewChain[i].getDataWindowViewFactoryCount() == 0;
        }
        return result;
    }

    /**
     * Joins require a remove stream: therefore a view is required for each stream, since all views post a remove stream.
     * <p>
     * If a view is polling or unidirectional, it does not require a view.
     * @param streamSpecs streams
     * @return analysis result
     * @throws ExprValidationException if constraints violated
     */
    private StreamJoinAnalysisResult verifyJoinViews(List<StreamSpecCompiled> streamSpecs,
            NamedWindowService namedWindowService) throws ExprValidationException {
        StreamJoinAnalysisResult analysisResult = new StreamJoinAnalysisResult(streamSpecs.size());
        if (streamSpecs.size() < 2) {
            return analysisResult;
        }

        // Determine if any stream has a unidirectional keyword

        // inspect unidirection indicator and named window flags
        int unidirectionalStreamNumber = -1;
        for (int i = 0; i < statementSpec.getStreamSpecs().size(); i++) {
            StreamSpecCompiled streamSpec = statementSpec.getStreamSpecs().get(i);
            if (streamSpec.getOptions().isUnidirectional()) {
                analysisResult.setUnidirectionalInd(i);
                if (unidirectionalStreamNumber != -1) {
                    throw new ExprValidationException(
                            "The unidirectional keyword can only apply to one stream in a join");
                }
                unidirectionalStreamNumber = i;
            }
            if (!streamSpec.getViewSpecs().isEmpty()) {
                analysisResult.setHasChildViews(i);
            }
            if (streamSpec instanceof NamedWindowConsumerStreamSpec) {
                NamedWindowConsumerStreamSpec nwSpec = (NamedWindowConsumerStreamSpec) streamSpec;
                analysisResult.setNamedWindow(i);
                NamedWindowProcessor processor = namedWindowService.getProcessor(nwSpec.getWindowName());
                if (processor.isVirtualDataWindow()) {
                    analysisResult.getViewExternal()[i] = processor.getVirtualDataWindow();
                }
            }
        }
        if ((unidirectionalStreamNumber != -1) && (analysisResult.getHasChildViews()[unidirectionalStreamNumber])) {
            throw new ExprValidationException(
                    "The unidirectional keyword requires that no views are declared onto the stream");
        }
        analysisResult.setUnidirectionalStreamNumber(unidirectionalStreamNumber);

        // count streams that provide data, excluding streams that poll data (DB and method)
        int countProviderNonpolling = 0;
        for (int i = 0; i < statementSpec.getStreamSpecs().size(); i++) {
            StreamSpecCompiled streamSpec = statementSpec.getStreamSpecs().get(i);
            if ((streamSpec instanceof MethodStreamSpec) || (streamSpec instanceof DBStatementStreamSpec)) {
                continue;
            }
            countProviderNonpolling++;
        }

        // if there is only one stream providing data, the analysis is done
        if (countProviderNonpolling == 1) {
            return analysisResult;
        }
        // there are multiple driving streams, verify the presence of a view for insert/remove stream

        // validation of join views works differently for unidirectional as there can be self-joins that don't require a view
        // see if this is a self-join in which all streams are filters and filter specification is the same.
        FilterSpecCompiled unidirectionalFilterSpec = null;
        FilterSpecCompiled lastFilterSpec = null;
        boolean pureSelfJoin = true;
        for (StreamSpecCompiled streamSpec : statementSpec.getStreamSpecs()) {
            if (!(streamSpec instanceof FilterStreamSpecCompiled)) {
                pureSelfJoin = false;
                continue;
            }

            FilterSpecCompiled filterSpec = ((FilterStreamSpecCompiled) streamSpec).getFilterSpec();
            if ((lastFilterSpec != null) && (!lastFilterSpec.equalsTypeAndFilter(filterSpec))) {
                pureSelfJoin = false;
            }
            if (!streamSpec.getViewSpecs().isEmpty()) {
                pureSelfJoin = false;
            }
            lastFilterSpec = filterSpec;

            if (streamSpec.getOptions().isUnidirectional()) {
                unidirectionalFilterSpec = filterSpec;
            }
        }

        // self-join without views and not unidirectional
        if ((pureSelfJoin) && (unidirectionalFilterSpec == null)) {
            analysisResult.setPureSelfJoin(true);
            return analysisResult;
        }

        // weed out filter and pattern streams that don't have a view in a join
        for (int i = 0; i < statementSpec.getStreamSpecs().size(); i++) {
            StreamSpecCompiled streamSpec = statementSpec.getStreamSpecs().get(i);
            if (!streamSpec.getViewSpecs().isEmpty()) {
                continue;
            }

            String name = streamSpec.getOptionalStreamName();
            if ((name == null) && (streamSpec instanceof FilterStreamSpecCompiled)) {
                name = ((FilterStreamSpecCompiled) streamSpec).getFilterSpec().getFilterForEventTypeName();
            }
            if ((name == null) && (streamSpec instanceof PatternStreamSpecCompiled)) {
                name = "pattern event stream";
            }

            if (streamSpec.getOptions().isUnidirectional()) {
                continue;
            }
            // allow a self-join without a child view, in that the filter spec is the same as the unidirection's stream filter
            if ((unidirectionalFilterSpec != null) && (streamSpec instanceof FilterStreamSpecCompiled)
                    && (((FilterStreamSpecCompiled) streamSpec).getFilterSpec()
                            .equalsTypeAndFilter(unidirectionalFilterSpec))) {
                analysisResult.setUnidirectionalNonDriving(i);
                continue;
            }
            if ((streamSpec instanceof FilterStreamSpecCompiled)
                    || (streamSpec instanceof PatternStreamSpecCompiled)) {
                throw new ExprValidationException(
                        "Joins require that at least one view is specified for each stream, no view was specified for "
                                + name);
            }
        }

        return analysisResult;
    }

    private Pair<Viewable, JoinPreloadMethod> handleJoin(String[] streamNames, EventType[] streamTypes,
            Viewable[] streamViews, ResultSetProcessor resultSetProcessor,
            SelectClauseStreamSelectorEnum selectStreamSelectorEnum, StatementContext statementContext,
            List<StopCallback> stopCallbacks, StreamJoinAnalysisResult joinAnalysisResult)
            throws ExprValidationException {
        // Handle joins
        final JoinSetComposerDesc composerDesc = statementContext.getJoinSetComposerFactory().makeComposer(
                statementSpec.getOuterJoinDescList(), statementSpec.getFilterRootNode(), streamTypes, streamNames,
                streamViews, selectStreamSelectorEnum, joinAnalysisResult, statementContext, queryPlanLogging,
                statementContext.getAnnotations());

        stopCallbacks.add(new StopCallback() {
            public void stop() {
                composerDesc.getJoinSetComposer().destroy();
            }
        });

        JoinSetFilter filter = new JoinSetFilter(composerDesc.getPostJoinFilterEvaluator());
        OutputProcessView indicatorView = OutputProcessViewFactory.makeView(resultSetProcessor, statementSpec,
                statementContext, services.getInternalEventRouter());

        // Create strategy for join execution
        JoinExecutionStrategy execution = new JoinExecutionStrategyImpl(composerDesc.getJoinSetComposer(), filter,
                indicatorView, statementContext);

        // The view needs a reference to the join execution to pull iterator values
        indicatorView.setJoinExecutionStrategy(execution);

        // Hook up dispatchable with buffer and execution strategy
        JoinExecStrategyDispatchable joinStatementDispatch = new JoinExecStrategyDispatchable(execution,
                statementSpec.getStreamSpecs().size());
        statementContext.getEpStatementHandle().setOptionalDispatchable(joinStatementDispatch);

        JoinPreloadMethod preloadMethod;
        if (joinAnalysisResult.getUnidirectionalStreamNumber() >= 0) {
            preloadMethod = new JoinPreloadMethodNull();
        } else {
            preloadMethod = new JoinPreloadMethodImpl(streamNames.length, composerDesc.getJoinSetComposer());
        }

        // Create buffer for each view. Point buffer to dispatchable for join.
        for (int i = 0; i < statementSpec.getStreamSpecs().size(); i++) {
            BufferView buffer = new BufferView(i);
            streamViews[i].addView(buffer);
            buffer.setObserver(joinStatementDispatch);
            preloadMethod.setBuffer(buffer, i);
        }

        return new Pair<Viewable, JoinPreloadMethod>(indicatorView, preloadMethod);
    }

    /**
     * Returns a stream name assigned for each stream, generated if none was supplied.
     * @param streams - stream specifications
     * @return array of stream names
     */
    @SuppressWarnings({ "StringContatenationInLoop" })
    protected static String[] determineStreamNames(List<StreamSpecCompiled> streams) {
        String[] streamNames = new String[streams.size()];
        for (int i = 0; i < streams.size(); i++) {
            // Assign a stream name for joins, if not supplied
            streamNames[i] = streams.get(i).getOptionalStreamName();
            if (streamNames[i] == null) {
                streamNames[i] = "stream_" + i;
            }
        }
        return streamNames;
    }

    /**
     * Validate filter and join expression nodes.
     * @param statementSpec the compiled statement
     * @param statementContext the statement services
     * @param typeService the event types for streams
     * @param viewResourceDelegate the delegate to verify expressions that use view resources
     */
    protected static void validateNodes(StatementSpecCompiled statementSpec, StatementContext statementContext,
            StreamTypeService typeService, ViewResourceDelegate viewResourceDelegate) {
        MethodResolutionService methodResolutionService = statementContext.getMethodResolutionService();

        if (statementSpec.getFilterRootNode() != null) {
            ExprNode optionalFilterNode = statementSpec.getFilterRootNode();

            // Validate where clause, initializing nodes to the stream ids used
            try {
                ExprValidationContext validationContext = new ExprValidationContext(typeService,
                        methodResolutionService, viewResourceDelegate, statementContext.getSchedulingService(),
                        statementContext.getVariableService(), statementContext,
                        statementContext.getEventAdapterService(), statementContext.getStatementName(),
                        statementContext.getStatementId(), statementContext.getAnnotations());
                optionalFilterNode = ExprNodeUtility.getValidatedSubtree(optionalFilterNode, validationContext);
                if (optionalFilterNode.getExprEvaluator().getType() != boolean.class
                        && optionalFilterNode.getExprEvaluator().getType() != Boolean.class) {
                    throw new ExprValidationException(
                            "The where-clause filter expression must return a boolean value");
                }
                statementSpec.setFilterExprRootNode(optionalFilterNode);

                // Make sure there is no aggregation in the where clause
                List<ExprAggregateNode> aggregateNodes = new LinkedList<ExprAggregateNode>();
                ExprAggregateNodeUtil.getAggregatesBottomUp(optionalFilterNode, aggregateNodes);
                if (!aggregateNodes.isEmpty()) {
                    throw new ExprValidationException(
                            "An aggregate function may not appear in a WHERE clause (use the HAVING clause)");
                }
            } catch (ExprValidationException ex) {
                log.debug(
                        ".validateNodes Validation exception for filter=" + optionalFilterNode.toExpressionString(),
                        ex);
                throw new EPStatementException("Error validating expression: " + ex.getMessage(),
                        statementContext.getExpression());
            }
        }

        if ((statementSpec.getOutputLimitSpec() != null)
                && (statementSpec.getOutputLimitSpec().getWhenExpressionNode() != null)) {
            ExprNode outputLimitWhenNode = statementSpec.getOutputLimitSpec().getWhenExpressionNode();

            // Validate where clause, initializing nodes to the stream ids used
            try {
                EventType outputLimitType = OutputConditionExpression
                        .getBuiltInEventType(statementContext.getEventAdapterService());
                StreamTypeService typeServiceOutputWhen = new StreamTypeServiceImpl(
                        new EventType[] { outputLimitType }, new String[] { null }, new boolean[] { true },
                        statementContext.getEngineURI(), false);
                ExprValidationContext validationContext = new ExprValidationContext(typeServiceOutputWhen,
                        methodResolutionService, null, statementContext.getSchedulingService(),
                        statementContext.getVariableService(), statementContext,
                        statementContext.getEventAdapterService(), statementContext.getStatementName(),
                        statementContext.getStatementId(), statementContext.getAnnotations());
                outputLimitWhenNode = ExprNodeUtility.getValidatedSubtree(outputLimitWhenNode, validationContext);
                statementSpec.getOutputLimitSpec().setWhenExpressionNode(outputLimitWhenNode);

                if (JavaClassHelper
                        .getBoxedType(outputLimitWhenNode.getExprEvaluator().getType()) != Boolean.class) {
                    throw new ExprValidationException(
                            "The when-trigger expression in the OUTPUT WHEN clause must return a boolean-type value");
                }
                validateNoAggregations(outputLimitWhenNode,
                        "An aggregate function may not appear in a OUTPUT LIMIT clause");

                if (statementSpec.getOutputLimitSpec().getThenExpressions() != null) {
                    for (OnTriggerSetAssignment assign : statementSpec.getOutputLimitSpec().getThenExpressions()) {
                        ExprNode node = ExprNodeUtility.getValidatedSubtree(assign.getExpression(),
                                validationContext);
                        assign.setExpression(node);
                        validateNoAggregations(node,
                                "An aggregate function may not appear in a OUTPUT LIMIT clause");
                    }
                }
            } catch (ExprValidationException ex) {
                throw new EPStatementException("Error validating expression: " + ex.getMessage(),
                        statementContext.getExpression());
            }
        }

        for (int outerJoinCount = 0; outerJoinCount < statementSpec.getOuterJoinDescList()
                .size(); outerJoinCount++) {
            OuterJoinDesc outerJoinDesc = statementSpec.getOuterJoinDescList().get(outerJoinCount);

            UniformPair<Integer> streamIdPair = validateOuterJoinPropertyPair(statementContext,
                    outerJoinDesc.getLeftNode(), outerJoinDesc.getRightNode(), outerJoinCount, typeService,
                    viewResourceDelegate);

            if (outerJoinDesc.getAdditionalLeftNodes() != null) {
                Set<Integer> streamSet = new HashSet<Integer>();
                streamSet.add(streamIdPair.getFirst());
                streamSet.add(streamIdPair.getSecond());
                for (int i = 0; i < outerJoinDesc.getAdditionalLeftNodes().length; i++) {
                    UniformPair<Integer> streamIdPairAdd = validateOuterJoinPropertyPair(statementContext,
                            outerJoinDesc.getAdditionalLeftNodes()[i], outerJoinDesc.getAdditionalRightNodes()[i],
                            outerJoinCount, typeService, viewResourceDelegate);

                    // make sure all additional properties point to the same two streams
                    if ((!streamSet.contains(streamIdPairAdd.getFirst())
                            || (!streamSet.contains(streamIdPairAdd.getSecond())))) {
                        String message = "Outer join ON-clause columns must refer to properties of the same joined streams"
                                + " when using multiple columns in the on-clause";
                        throw new EPStatementException("Error validating expression: " + message,
                                statementContext.getExpression());
                    }

                }
            }
        }
    }

    private static void validateNoAggregations(ExprNode exprNode, String errorMsg) throws ExprValidationException {
        // Make sure there is no aggregation in the where clause
        List<ExprAggregateNode> aggregateNodes = new LinkedList<ExprAggregateNode>();
        ExprAggregateNodeUtil.getAggregatesBottomUp(exprNode, aggregateNodes);
        if (!aggregateNodes.isEmpty()) {
            throw new ExprValidationException(errorMsg);
        }
    }

    private static UniformPair<Integer> validateOuterJoinPropertyPair(StatementContext statementContext,
            ExprIdentNode leftNode, ExprIdentNode rightNode, int outerJoinCount, StreamTypeService typeService,
            ViewResourceDelegate viewResourceDelegate) {
        // Validate the outer join clause using an artificial equals-node on top.
        // Thus types are checked via equals.
        // Sets stream ids used for validated nodes.
        ExprNode equalsNode = new ExprEqualsNodeImpl(false, false);
        equalsNode.addChildNode(leftNode);
        equalsNode.addChildNode(rightNode);
        try {
            ExprValidationContext validationContext = new ExprValidationContext(typeService,
                    statementContext.getMethodResolutionService(), viewResourceDelegate,
                    statementContext.getSchedulingService(), statementContext.getVariableService(),
                    statementContext, statementContext.getEventAdapterService(),
                    statementContext.getStatementName(), statementContext.getStatementId(),
                    statementContext.getAnnotations());
            ExprNodeUtility.getValidatedSubtree(equalsNode, validationContext);
        } catch (ExprValidationException ex) {
            log.debug("Validation exception for outer join node=" + equalsNode.toExpressionString(), ex);
            throw new EPStatementException("Error validating expression: " + ex.getMessage(),
                    statementContext.getExpression());
        }

        // Make sure we have left-hand-side and right-hand-side refering to different streams
        int streamIdLeft = leftNode.getStreamId();
        int streamIdRight = rightNode.getStreamId();
        if (streamIdLeft == streamIdRight) {
            String message = "Outer join ON-clause cannot refer to properties of the same stream";
            throw new EPStatementException("Error validating expression: " + message,
                    statementContext.getExpression());
        }

        // Make sure one of the properties refers to the acutual stream currently being joined
        int expectedStreamJoined = outerJoinCount + 1;
        if ((streamIdLeft != expectedStreamJoined) && (streamIdRight != expectedStreamJoined)) {
            String message = "Outer join ON-clause must refer to at least one property of the joined stream"
                    + " for stream " + expectedStreamJoined;
            throw new EPStatementException("Error validating expression: " + message,
                    statementContext.getExpression());
        }

        // Make sure neither of the streams refer to a 'future' stream
        String badPropertyName = null;
        if (streamIdLeft > outerJoinCount + 1) {
            badPropertyName = leftNode.getResolvedPropertyName();
        }
        if (streamIdRight > outerJoinCount + 1) {
            badPropertyName = rightNode.getResolvedPropertyName();
        }
        if (badPropertyName != null) {
            String message = "Outer join ON-clause invalid scope for property" + " '" + badPropertyName
                    + "', expecting the current or a prior stream scope";
            throw new EPStatementException("Error validating expression: " + message,
                    statementContext.getExpression());
        }

        return new UniformPair<Integer>(streamIdLeft, streamIdRight);
    }

    private Viewable handleSimpleSelect(Viewable view, ResultSetProcessor resultSetProcessor,
            StatementContext statementContext) throws ExprValidationException {
        Viewable finalView = view;

        // Add filter view that evaluates the filter expression
        if (statementSpec.getFilterRootNode() != null) {
            FilterExprView filterView = new FilterExprView(statementSpec.getFilterRootNode().getExprEvaluator(),
                    statementContext);
            finalView.addView(filterView);
            finalView = filterView;
        }

        // for ordered deliver without output limit/buffer
        if (!statementSpec.getOrderByList().isEmpty() && (statementSpec.getOutputLimitSpec() == null)) {
            SingleStreamDispatchView bf = new SingleStreamDispatchView();
            statementContext.getEpStatementHandle().setOptionalDispatchable(bf);
            finalView.addView(bf);
            finalView = bf;
        }

        OutputProcessView selectView = OutputProcessViewFactory.makeView(resultSetProcessor, statementSpec,
                statementContext, services.getInternalEventRouter());

        finalView.addView(selectView);
        finalView = selectView;

        return finalView;
    }

    private SubSelectStreamCollection createSubSelectStreams(boolean isJoin, Annotation[] annotations)
            throws ExprValidationException, ViewProcessingException {
        SubSelectStreamCollection subSelectStreamDesc = new SubSelectStreamCollection();
        int subselectStreamNumber = 1024;

        // Process all subselect expression nodes
        for (ExprSubselectNode subselect : statementSpec.getSubSelectExpressions()) {
            StatementSpecCompiled statementSpec = subselect.getStatementSpecCompiled();

            if (statementSpec.getStreamSpecs().get(0) instanceof FilterStreamSpecCompiled) {
                FilterStreamSpecCompiled filterStreamSpec = (FilterStreamSpecCompiled) statementSpec
                        .getStreamSpecs().get(0);

                // A child view is required to limit the stream
                if (filterStreamSpec.getViewSpecs().size() == 0) {
                    throw new ExprValidationException(
                            "Subqueries require one or more views to limit the stream, consider declaring a length or time window");
                }

                subselectStreamNumber++;

                // Register filter, create view factories
                Pair<EventStream, StatementLock> streamLockPair = services.getStreamService().createStream(
                        statementContext.getStatementId(), filterStreamSpec.getFilterSpec(),
                        statementContext.getFilterService(), statementContext.getEpStatementHandle(), isJoin, true,
                        statementContext, false, false, statementContext.getAnnotations());
                Viewable viewable = streamLockPair.getFirst();
                ViewFactoryChain viewFactoryChain = services.getViewService().createFactories(subselectStreamNumber,
                        viewable.getEventType(), filterStreamSpec.getViewSpecs(), filterStreamSpec.getOptions(),
                        statementContext);
                subselect.setRawEventType(viewFactoryChain.getEventType());

                // Add lookup to list, for later starts
                subSelectStreamDesc.add(subselect, subselectStreamNumber, viewable, viewFactoryChain);
            } else {
                NamedWindowConsumerStreamSpec namedSpec = (NamedWindowConsumerStreamSpec) statementSpec
                        .getStreamSpecs().get(0);
                NamedWindowProcessor processor = services.getNamedWindowService()
                        .getProcessor(namedSpec.getWindowName());

                // if named-window index sharing is disabled (the default) or filter expressions are provided then consume the insert-remove stream
                boolean disableIndexShare = HintEnum.DISABLE_WINDOW_SUBQUERY_INDEXSHARE
                        .getHint(annotations) != null;
                if (!namedSpec.getFilterExpressions().isEmpty() || !processor.isEnableSubqueryIndexShare()
                        || disableIndexShare) {
                    NamedWindowConsumerView consumerView = processor.addConsumer(namedSpec.getFilterExpressions(),
                            namedSpec.getOptPropertyEvaluator(), statementContext.getEpStatementHandle(),
                            statementContext.getStatementStopService());
                    ViewFactoryChain viewFactoryChain = services.getViewService().createFactories(0,
                            consumerView.getEventType(), namedSpec.getViewSpecs(), namedSpec.getOptions(),
                            statementContext);
                    subselect.setRawEventType(viewFactoryChain.getEventType());
                    subSelectStreamDesc.add(subselect, subselectStreamNumber, consumerView, viewFactoryChain);
                }
                // else if there are no named window stream filter expressions and index sharing is enabled
                else {
                    ViewFactoryChain viewFactoryChain = services.getViewService().createFactories(0,
                            processor.getNamedWindowType(), namedSpec.getViewSpecs(), namedSpec.getOptions(),
                            statementContext);
                    subselect.setRawEventType(processor.getNamedWindowType());
                    subSelectStreamDesc.add(subselect, subselectStreamNumber, null, viewFactoryChain);
                }
            }
        }

        return subSelectStreamDesc;
    }

    private void startSubSelect(SubSelectStreamCollection subSelectStreamDesc, String[] outerStreamNames,
            EventType[] outerEventTypesSelect, String[] outerEventTypeNamees, List<StopCallback> stopCallbacks,
            Annotation[] annotations, List<ExprDeclaredNode> declaredExpressions) throws ExprValidationException {
        boolean fullTableScan = HintEnum.SET_NOINDEX.getHint(annotations) != null;
        int subqueryNum = 0;
        for (ExprSubselectNode subselect : statementSpec.getSubSelectExpressions()) {
            if (queryPlanLogging && queryPlanLog.isInfoEnabled()) {
                queryPlanLog.info(
                        "For statement '" + statementContext.getStatementName() + "' subquery " + subqueryNum);
            }

            StatementSpecCompiled statementSpec = subselect.getStatementSpecCompiled();
            StreamSpecCompiled filterStreamSpec = statementSpec.getStreamSpecs().get(0);

            String subselecteventTypeName = null;
            if (filterStreamSpec instanceof FilterStreamSpecCompiled) {
                subselecteventTypeName = ((FilterStreamSpecCompiled) filterStreamSpec).getFilterSpec()
                        .getFilterForEventTypeName();
            } else if (filterStreamSpec instanceof NamedWindowConsumerStreamSpec) {
                subselecteventTypeName = ((NamedWindowConsumerStreamSpec) filterStreamSpec).getWindowName();
            }

            ViewFactoryChain viewFactoryChain = subSelectStreamDesc.getViewFactoryChain(subselect);
            EventType eventType = viewFactoryChain.getEventType();

            // determine a stream name unless one was supplied
            String subexpressionStreamName = filterStreamSpec.getOptionalStreamName();
            int subselectStreamNumber = subSelectStreamDesc.getStreamNumber(subselect);
            if (subexpressionStreamName == null) {
                subexpressionStreamName = "$subselect_" + subselectStreamNumber;
            }

            // Named windows don't allow data views
            if (filterStreamSpec instanceof NamedWindowConsumerStreamSpec) {
                ViewResourceDelegate viewResourceDelegate = new ViewResourceDelegateImpl(
                        new ViewFactoryChain[] { viewFactoryChain }, statementContext);
                viewResourceDelegate.requestCapability(0, new NotADataWindowViewCapability(), null);
            }

            // Expression declarations are copies of a predefined expression body with their own stream context.
            // Should only be invoked if the subselect belongs to that instance.
            if (!declaredExpressions.isEmpty()) {
                // Find that subselect within that declaration
                ExprNodeSubselectVisitor visitor = new ExprNodeSubselectVisitor();
                for (ExprDeclaredNode declaration : declaredExpressions) {
                    visitor.reset();
                    declaration.accept(visitor);
                    if (visitor.getSubselects().contains(subselect)) {
                        declaration.setSubselectOuterStreamNames(outerStreamNames, outerEventTypesSelect,
                                outerEventTypeNamees, services.getEngineURI(), subselect, subexpressionStreamName,
                                eventType, subselecteventTypeName);
                    }
                }
            }

            EventType[] outerEventTypes;
            StreamTypeService subselectTypeService;

            // Use the override provided by the subselect if applicable
            if (subselect.getFilterSubqueryStreamTypes() != null) {
                subselectTypeService = subselect.getFilterSubqueryStreamTypes();
                outerEventTypes = new EventType[subselectTypeService.getEventTypes().length - 1];
                System.arraycopy(subselectTypeService.getEventTypes(), 1, outerEventTypes, 0,
                        subselectTypeService.getEventTypes().length - 1);
            } else {
                // Streams event types are the original stream types with the stream zero the subselect stream
                LinkedHashMap<String, Pair<EventType, String>> namesAndTypes = new LinkedHashMap<String, Pair<EventType, String>>();
                namesAndTypes.put(subexpressionStreamName,
                        new Pair<EventType, String>(eventType, subselecteventTypeName));
                for (int i = 0; i < outerEventTypesSelect.length; i++) {
                    Pair<EventType, String> pair = new Pair<EventType, String>(outerEventTypesSelect[i],
                            outerEventTypeNamees[i]);
                    namesAndTypes.put(outerStreamNames[i], pair);
                }
                subselectTypeService = new StreamTypeServiceImpl(namesAndTypes, services.getEngineURI(), true,
                        true);
                outerEventTypes = outerEventTypesSelect;
            }
            ViewResourceDelegate viewResourceDelegateSubselect = new ViewResourceDelegateImpl(
                    new ViewFactoryChain[] { viewFactoryChain }, statementContext);

            // Validate select expression
            SelectClauseSpecCompiled selectClauseSpec = subselect.getStatementSpecCompiled().getSelectClauseSpec();
            AggregationService aggregationService = null;
            List<ExprNode> selectExpressions = new ArrayList<ExprNode>();
            List<String> assignedNames = new ArrayList<String>();
            boolean isWildcard = false;
            boolean isStreamWildcard = false;
            if (selectClauseSpec.getSelectExprList().size() > 0) {
                List<ExprAggregateNode> aggExprNodes = new LinkedList<ExprAggregateNode>();

                ExprValidationContext validationContext = new ExprValidationContext(subselectTypeService,
                        statementContext.getMethodResolutionService(), viewResourceDelegateSubselect,
                        statementContext.getSchedulingService(), statementContext.getVariableService(),
                        statementContext, statementContext.getEventAdapterService(),
                        statementContext.getStatementName(), statementContext.getStatementId(),
                        statementContext.getAnnotations());
                for (int i = 0; i < selectClauseSpec.getSelectExprList().size(); i++) {
                    SelectClauseElementCompiled element = selectClauseSpec.getSelectExprList().get(i);

                    if (element instanceof SelectClauseExprCompiledSpec) {
                        // validate
                        SelectClauseExprCompiledSpec compiled = (SelectClauseExprCompiledSpec) element;
                        ExprNode selectExpression = compiled.getSelectExpression();
                        selectExpression = ExprNodeUtility.getValidatedSubtree(selectExpression, validationContext);

                        selectExpressions.add(selectExpression);
                        assignedNames.add(compiled.getAssignedName());

                        // handle aggregation
                        ExprAggregateNodeUtil.getAggregatesBottomUp(selectExpression, aggExprNodes);

                        if (aggExprNodes.size() > 0) {
                            // This stream (stream 0) properties must either all be under aggregation, or all not be.
                            List<Pair<Integer, String>> propertiesNotAggregated = getExpressionProperties(
                                    selectExpression, false);
                            for (Pair<Integer, String> pair : propertiesNotAggregated) {
                                if (pair.getFirst() == 0) {
                                    throw new ExprValidationException(
                                            "Subselect properties must all be within aggregation functions");
                                }
                            }
                        }
                    } else if (element instanceof SelectClauseElementWildcard) {
                        isWildcard = true;
                    } else if (element instanceof SelectClauseStreamCompiledSpec) {
                        isStreamWildcard = true;
                    }
                } // end of for loop

                if (!selectExpressions.isEmpty()) {
                    subselect.setSelectClause(selectExpressions.toArray(new ExprNode[selectExpressions.size()]));
                    subselect.setSelectAsNames(assignedNames.toArray(new String[assignedNames.size()]));
                    if (isWildcard || isStreamWildcard) {
                        throw new ExprValidationException(
                                "Subquery multi-column select does not allow wildcard or stream wildcard when selecting multiple columns.");
                    }
                    if (selectExpressions.size() > 1 && !subselect.isAllowMultiColumnSelect()) {
                        throw new ExprValidationException(
                                "Subquery multi-column select is not allowed in this context.");
                    }
                    if ((selectExpressions.size() > 1 && aggExprNodes.size() > 0)) {
                        // all properties must be aggregated
                        if (!ExprNodeUtility.getNonAggregatedProps(selectExpressions).isEmpty()) {
                            throw new ExprValidationException(
                                    "Subquery with multi-column select requires that either all or none of the selected columns are under aggregation.");
                        }
                    }
                }

                if (aggExprNodes.size() > 0) {
                    List<ExprAggregateNode> havingAgg = Collections.emptyList();
                    List<ExprAggregateNode> orderByAgg = Collections.emptyList();
                    aggregationService = AggregationServiceFactory.getService(aggExprNodes, havingAgg, orderByAgg,
                            false, statementContext.getMethodResolutionService(), statementContext, annotations,
                            statementContext.getVariableService(), statementContext.getStatementStopService(),
                            false, statementSpec.getFilterRootNode(), statementSpec.getHavingExprRootNode());

                    // Other stream properties, if there is aggregation, cannot be under aggregation.
                    for (ExprAggregateNode aggNode : aggExprNodes) {
                        List<Pair<Integer, String>> propertiesNodesAggregated = getExpressionProperties(aggNode,
                                true);
                        for (Pair<Integer, String> pair : propertiesNodesAggregated) {
                            if (pair.getFirst() != 0) {
                                throw new ExprValidationException(
                                        "Subselect aggregation functions cannot aggregate across correlated properties");
                            }
                        }
                    }
                }
            }

            // no aggregation functions allowed in filter
            if (statementSpec.getFilterRootNode() != null) {
                List<ExprAggregateNode> aggExprNodesFilter = new LinkedList<ExprAggregateNode>();
                ExprAggregateNodeUtil.getAggregatesBottomUp(statementSpec.getFilterRootNode(), aggExprNodesFilter);
                if (aggExprNodesFilter.size() > 0) {
                    throw new ExprValidationException(
                            "Aggregation functions are not supported within subquery filters, consider using insert-into instead");
                }
            }

            // Validate filter expression, if there is one
            ExprNode filterExpr = statementSpec.getFilterRootNode();
            boolean correlatedSubquery = false;
            if (filterExpr != null) {
                ExprValidationContext validationContext = new ExprValidationContext(subselectTypeService,
                        statementContext.getMethodResolutionService(), viewResourceDelegateSubselect,
                        statementContext.getSchedulingService(), statementContext.getVariableService(),
                        statementContext, statementContext.getEventAdapterService(),
                        statementContext.getStatementName(), statementContext.getStatementId(),
                        statementContext.getAnnotations());
                filterExpr = ExprNodeUtility.getValidatedSubtree(filterExpr, validationContext);
                if (JavaClassHelper.getBoxedType(filterExpr.getExprEvaluator().getType()) != Boolean.class) {
                    throw new ExprValidationException("Subselect filter expression must return a boolean value");
                }

                // check the presence of a correlated filter, not allowed with aggregation
                ExprNodeIdentifierVisitor visitor = new ExprNodeIdentifierVisitor(true);
                filterExpr.accept(visitor);
                List<Pair<Integer, String>> propertiesNodes = visitor.getExprProperties();
                for (Pair<Integer, String> pair : propertiesNodes) {
                    if (pair.getFirst() != 0) {
                        correlatedSubquery = true;
                        break;
                    }
                }
            }

            // Finally create views
            Viewable viewableRoot = subSelectStreamDesc.getRootViewable(subselect);
            Viewable subselectView = services.getViewService().createViews(viewableRoot,
                    viewFactoryChain.getViewFactoryChain(), statementContext);

            // If we do aggregation, then the view results must be added and removed from aggregation
            final EventTable eventIndex;
            // Under aggregation conditions, there is no lookup/corelated subquery strategy, and
            // the view-supplied events are simply aggregated, a null-event supplied to the stream for the select-clause, and not kept in index.
            // Note that "var1 + max(var2)" is not allowed as some properties are not under aggregation (which event to use?).
            if (aggregationService != null) {
                subselect.setStrategy(new SubordTableLookupStrategyNullRow());
                subselect.setFilterExpr(null); // filter not evaluated by subselect expression as not correlated
                subselect.setAggregatedSubquery(true);
                ExprEvaluator filterExprEval = (filterExpr == null) ? null : filterExpr.getExprEvaluator();

                // If we have window index sharing, the subselectView will be null
                if (!correlatedSubquery && subselectView != null) {
                    SubselectAggregatorView aggregatorView = new SubselectAggregatorView(aggregationService,
                            filterExprEval, statementContext);
                    subselectView.addView(aggregatorView);
                    subselectView = aggregatorView;
                    eventIndex = null;
                } else {
                    Pair<EventTable, SubordTableLookupStrategy> indexPair = determineSubqueryIndex(filterExpr,
                            eventType, outerEventTypes, subselectTypeService, fullTableScan);
                    subselect.setStrategy(indexPair.getSecond());
                    eventIndex = indexPair.getFirst();

                    SubselectAggregationPreprocessor preprocessor = new SubselectAggregationPreprocessor(
                            aggregationService, filterExpr.getExprEvaluator());
                    subselect.setSubselectAggregationPreprocessor(preprocessor);
                }
            } else {
                // Determine indexing of the filter expression
                Pair<EventTable, SubordTableLookupStrategy> indexPair = determineSubqueryIndex(filterExpr,
                        eventType, outerEventTypes, subselectTypeService, fullTableScan);
                ExprEvaluator filterExprEval = (filterExpr == null) ? null : filterExpr.getExprEvaluator();
                subselect.setStrategy(indexPair.getSecond());
                subselect.setFilterExpr(filterExprEval);
                eventIndex = indexPair.getFirst();
            }

            boolean disableIndexShare = HintEnum.DISABLE_WINDOW_SUBQUERY_INDEXSHARE.getHint(annotations) != null;
            SubordTableLookupStrategy namedWindowSubqueryLookup = null;
            if ((filterStreamSpec instanceof NamedWindowConsumerStreamSpec) && (!disableIndexShare)) {
                NamedWindowConsumerStreamSpec namedSpec = (NamedWindowConsumerStreamSpec) filterStreamSpec;
                if (namedSpec.getFilterExpressions().isEmpty()) {
                    NamedWindowProcessor processor = services.getNamedWindowService()
                            .getProcessor(namedSpec.getWindowName());
                    if (processor.isEnableSubqueryIndexShare()) {
                        if (queryPlanLogging && queryPlanLog.isInfoEnabled()) {
                            queryPlanLog.info("prefering shared index");
                        }
                        SubordPropPlan joinedPropPlan = QueryPlanIndexBuilder.getJoinProps(filterExpr,
                                outerEventTypes.length, subselectTypeService.getEventTypes());
                        namedWindowSubqueryLookup = processor.getRootView().getAddSubqueryLookupStrategy(
                                statementContext.getStatementName(), outerEventTypesSelect, joinedPropPlan,
                                fullTableScan);
                        subselect.setStrategy(namedWindowSubqueryLookup);
                        stopCallbacks
                                .add(new NamedWindowSubqueryStopCallback(processor, namedWindowSubqueryLookup));
                    }
                }
            }

            // Clear out index on statement stop
            if (namedWindowSubqueryLookup == null) {
                stopCallbacks.add(new SubqueryStopCallback(eventIndex));
            }

            // Preload
            if (namedWindowSubqueryLookup == null) {
                if (filterStreamSpec instanceof NamedWindowConsumerStreamSpec) {
                    NamedWindowConsumerStreamSpec namedSpec = (NamedWindowConsumerStreamSpec) filterStreamSpec;
                    NamedWindowProcessor processor = services.getNamedWindowService()
                            .getProcessor(namedSpec.getWindowName());
                    NamedWindowTailView consumerView = processor.getTailView();

                    // preload view for stream
                    ArrayList<EventBean> eventsInWindow = new ArrayList<EventBean>();
                    if (namedSpec.getFilterExpressions() != null) {
                        EventBean[] events = new EventBean[1];
                        for (EventBean event : consumerView) {
                            events[0] = event;
                            boolean add = true;
                            for (ExprNode filter : namedSpec.getFilterExpressions()) {
                                Object result = filter.getExprEvaluator().evaluate(events, true, statementContext);
                                if ((result == null) || (!((Boolean) result))) {
                                    add = false;
                                    break;
                                }
                            }
                            if (add) {
                                eventsInWindow.add(events[0]);
                            }
                        }
                    } else {
                        for (Iterator<EventBean> it = consumerView.iterator(); it.hasNext();) {
                            eventsInWindow.add(it.next());
                        }
                    }
                    EventBean[] newEvents = eventsInWindow.toArray(new EventBean[eventsInWindow.size()]);
                    ((View) viewableRoot).update(newEvents, null); // fill view
                    if (eventIndex != null) {
                        eventIndex.add(newEvents); // fill index
                    }
                } else // preload from the data window that sit on top
                {
                    // Start up event table from the iterator
                    Iterator<EventBean> it = subselectView.iterator();
                    if ((it != null) && (it.hasNext())) {
                        ArrayList<EventBean> preloadEvents = new ArrayList<EventBean>();
                        for (; it.hasNext();) {
                            preloadEvents.add(it.next());
                        }
                        if (eventIndex != null) {
                            eventIndex.add(preloadEvents.toArray(new EventBean[preloadEvents.size()]));
                        }
                    }
                }

                subqueryNum++;
            }

            // hook up subselect viewable and event table
            if (subselectView != null) {
                BufferView bufferView = new BufferView(subselectStreamNumber);
                bufferView.setObserver(new SubselectBufferObserver(eventIndex));
                subselectView.addView(bufferView);
            }
        }
    }

    private Pair<EventTable, SubordTableLookupStrategy> determineSubqueryIndex(ExprNode filterExpr,
            EventType viewableEventType, EventType[] outerEventTypes, StreamTypeService subselectTypeService,
            boolean fullTableScan) throws ExprValidationException {
        Pair<EventTable, SubordTableLookupStrategy> result = determineSubqueryIndexInternal(filterExpr,
                viewableEventType, outerEventTypes, subselectTypeService, fullTableScan);

        if (queryPlanLogging && queryPlanLog.isInfoEnabled()) {
            queryPlanLog.info("local index");
            queryPlanLog.info("strategy " + result.getSecond().toQueryPlan());
            queryPlanLog.info("table " + result.getFirst().toQueryPlan());
        }

        return result;
    }

    private Pair<EventTable, SubordTableLookupStrategy> determineSubqueryIndexInternal(ExprNode filterExpr,
            EventType viewableEventType, EventType[] outerEventTypes, StreamTypeService subselectTypeService,
            boolean fullTableScan) throws ExprValidationException {
        // No filter expression means full table scan
        if ((filterExpr == null) || fullTableScan) {
            UnindexedEventTable table = new UnindexedEventTable(0);
            SubordFullTableScanLookupStrategy strategy = new SubordFullTableScanLookupStrategy(table);
            return new Pair<EventTable, SubordTableLookupStrategy>(table, strategy);
        }

        // Build a list of streams and indexes
        SubordPropPlan joinPropDesc = QueryPlanIndexBuilder.getJoinProps(filterExpr, outerEventTypes.length,
                subselectTypeService.getEventTypes());
        Map<String, SubordPropHashKey> hashKeys = joinPropDesc.getHashProps();
        Map<String, SubordPropRangeKey> rangeKeys = joinPropDesc.getRangeProps();
        List<SubordPropHashKey> hashKeyList = new ArrayList<SubordPropHashKey>(hashKeys.values());
        List<SubordPropRangeKey> rangeKeyList = new ArrayList<SubordPropRangeKey>(rangeKeys.values());

        // build table (local table)
        EventTable eventTable;
        CoercionDesc hashCoercionDesc;
        CoercionDesc rangeCoercionDesc;
        if (hashKeys.size() != 0 && rangeKeys.isEmpty()) {
            String indexedProps[] = hashKeys.keySet().toArray(new String[hashKeys.keySet().size()]);
            hashCoercionDesc = CoercionUtil.getCoercionTypesHash(viewableEventType, indexedProps, hashKeyList);
            rangeCoercionDesc = new CoercionDesc(false, null);

            if (hashKeys.size() == 1) {
                if (!hashCoercionDesc.isCoerce()) {
                    eventTable = new PropertyIndexedEventTableSingle(0, viewableEventType, indexedProps[0]);
                } else {
                    eventTable = new PropertyIndexedEventTableSingleCoerceAdd(0, viewableEventType, indexedProps[0],
                            hashCoercionDesc.getCoercionTypes()[0]);
                }
            } else {
                if (!hashCoercionDesc.isCoerce()) {
                    eventTable = new PropertyIndexedEventTable(0, viewableEventType, indexedProps);
                } else {
                    eventTable = new PropertyIndexedEventTableCoerceAdd(0, viewableEventType, indexedProps,
                            hashCoercionDesc.getCoercionTypes());
                }
            }
        } else if (hashKeys.isEmpty() && rangeKeys.isEmpty()) {
            eventTable = new UnindexedEventTable(0);
            hashCoercionDesc = new CoercionDesc(false, null);
            rangeCoercionDesc = new CoercionDesc(false, null);
        } else if (hashKeys.isEmpty() && rangeKeys.size() == 1) {
            String indexedProp = rangeKeys.keySet().iterator().next();
            CoercionDesc coercionRangeTypes = CoercionUtil.getCoercionTypesRange(viewableEventType, rangeKeys,
                    outerEventTypes);
            if (!coercionRangeTypes.isCoerce()) {
                eventTable = new PropertySortedEventTable(0, viewableEventType, indexedProp);
            } else {
                eventTable = new PropertySortedEventTableCoerced(0, viewableEventType, indexedProp,
                        coercionRangeTypes.getCoercionTypes()[0]);
            }
            hashCoercionDesc = new CoercionDesc(false, null);
            rangeCoercionDesc = coercionRangeTypes;
        } else {
            String[] indexedKeyProps = hashKeys.keySet().toArray(new String[hashKeys.keySet().size()]);
            Class[] coercionKeyTypes = SubordPropUtil.getCoercionTypes(hashKeys.values());
            String[] indexedRangeProps = rangeKeys.keySet().toArray(new String[rangeKeys.keySet().size()]);
            CoercionDesc coercionRangeTypes = CoercionUtil.getCoercionTypesRange(viewableEventType, rangeKeys,
                    outerEventTypes);
            eventTable = new PropertyCompositeEventTable(0, viewableEventType, indexedKeyProps, coercionKeyTypes,
                    indexedRangeProps, coercionRangeTypes.getCoercionTypes());
            hashCoercionDesc = CoercionUtil.getCoercionTypesHash(viewableEventType, indexedKeyProps, hashKeyList);
            rangeCoercionDesc = coercionRangeTypes;
        }

        SubordTableLookupStrategy subqTableLookupStrategy = SubordinateTableLookupStrategyFactory.getLookupStrategy(
                outerEventTypes, hashKeyList, hashCoercionDesc, rangeKeyList, rangeCoercionDesc, false, eventTable);

        return new Pair<EventTable, SubordTableLookupStrategy>(eventTable, subqTableLookupStrategy);
    }

    // For delete actions from named windows
    private ExprNode validateJoinNamedWindow(ExprNode deleteJoinExpr, EventType namedWindowType,
            String namedWindowStreamName, String namedWindowName, EventType filteredType, String filterStreamName,
            String filteredTypeName) throws ExprValidationException {
        if (deleteJoinExpr == null) {
            return null;
        }

        LinkedHashMap<String, Pair<EventType, String>> namesAndTypes = new LinkedHashMap<String, Pair<EventType, String>>();
        namesAndTypes.put(namedWindowStreamName, new Pair<EventType, String>(namedWindowType, namedWindowName));
        namesAndTypes.put(filterStreamName, new Pair<EventType, String>(filteredType, filteredTypeName));
        StreamTypeService typeService = new StreamTypeServiceImpl(namesAndTypes, services.getEngineURI(), false,
                false);

        ExprValidationContext validationContext = new ExprValidationContext(typeService,
                statementContext.getMethodResolutionService(), null, statementContext.getSchedulingService(),
                statementContext.getVariableService(), statementContext, statementContext.getEventAdapterService(),
                statementContext.getStatementName(), statementContext.getStatementId(),
                statementContext.getAnnotations());
        return ExprNodeUtility.getValidatedSubtree(deleteJoinExpr, validationContext);
    }

    /**
     * Walk expression returning properties used.
     * @param exprNode to walk
     * @param visitAggregateNodes true to visit aggregation nodes
     * @return list of props
     */
    public static List<Pair<Integer, String>> getExpressionProperties(ExprNode exprNode,
            boolean visitAggregateNodes) {
        ExprNodeIdentifierVisitor visitor = new ExprNodeIdentifierVisitor(visitAggregateNodes);
        exprNode.accept(visitor);
        return visitor.getExprProperties();
    }
}