/************************************************************************************** * Copyright (C) 2008 EsperTech, Inc. All rights reserved. * * http://esper.codehaus.org * * http://www.espertech.com * * ---------------------------------------------------------------------------------- * * The software in this package is published under the terms of the GPL license * * a copy of which has been included with this distribution in the license.txt file. * **************************************************************************************/ package com.espertech.esper.epl.join.plan; import com.espertech.esper.client.EventType; import com.espertech.esper.collection.Pair; import com.espertech.esper.type.OuterJoinType; import java.util.Collections; import java.util.List; /** * Builds a query plan for the simple 2-stream scenario. */ public class TwoStreamQueryPlanBuilder { /** * Build query plan. * @param queryGraph - navigability info * @param optionalOuterJoinType - outer join type, null if not an outer join * @param typesPerStream - event types for each stream * @return query plan */ public static QueryPlan build(EventType[] typesPerStream, QueryGraph queryGraph, OuterJoinType optionalOuterJoinType, String[][][] uniqueIndexProps) { QueryPlanIndex[] indexSpecs = new QueryPlanIndex[2]; QueryPlanNode[] execNodeSpecs = new QueryPlanNode[2]; TableLookupPlan lookupPlans[] = new TableLookupPlan[2]; // plan lookup from 1 to zero Pair<QueryPlanIndex, TableLookupPlan> plan = planQuery(1, 0, typesPerStream, queryGraph, uniqueIndexProps[0]); indexSpecs[0] = plan.getFirst(); lookupPlans[1] = plan.getSecond(); // plan lookup from zero to 1 plan = planQuery(0, 1, typesPerStream, queryGraph, uniqueIndexProps[1]); indexSpecs[1] = plan.getFirst(); lookupPlans[0] = plan.getSecond(); execNodeSpecs[0] = new TableLookupNode(lookupPlans[0]); execNodeSpecs[1] = new TableLookupNode(lookupPlans[1]); if (optionalOuterJoinType != null) { if ( (optionalOuterJoinType.equals(OuterJoinType.LEFT)) || (optionalOuterJoinType.equals(OuterJoinType.FULL)) ) { execNodeSpecs[0] = new TableOuterLookupNode(lookupPlans[0]); } if ( (optionalOuterJoinType.equals(OuterJoinType.RIGHT)) || (optionalOuterJoinType.equals(OuterJoinType.FULL)) ) { execNodeSpecs[1] = new TableOuterLookupNode(lookupPlans[1]); } } return new QueryPlan(indexSpecs, execNodeSpecs); } private static Pair<QueryPlanIndex, TableLookupPlan> planQuery(int lookupStream, int indexedStream, EventType[] typesPerStream, QueryGraph queryGraph, String[][] indexStreamsUniqueProps) { // not navigable, full table scan if (!queryGraph.isNavigableAtAll(lookupStream, indexedStream)) { QueryPlanIndex index = QueryPlanIndex.makeIndex(new QueryPlanIndexItem(null, null, null, null, false)); FullTableScanLookupPlan plan = new FullTableScanLookupPlan(lookupStream, indexedStream, index.getFirstIndexNum()); return new Pair<QueryPlanIndex, TableLookupPlan>(index, plan); } QueryGraphValue queryGraphValue = queryGraph.getGraphValue(lookupStream, indexedStream); // determine hash coercion types - these are the same regardless of direction QueryGraphValuePairHashKeyIndex hashKeyIndexPair = queryGraphValue.getHashKeyProps(); List<QueryGraphValueEntryHashKeyed> hashKeyFunctions = hashKeyIndexPair.getKeys(); String[] hashIndexProps = hashKeyIndexPair.getIndexed(); CoercionDesc hashCoercionTypesDesc = CoercionUtil.getCoercionTypesHash(typesPerStream, lookupStream, indexedStream, hashKeyFunctions, hashIndexProps); Class[] hashCoercionTypes = hashCoercionTypesDesc.isCoerce() ? hashCoercionTypesDesc.getCoercionTypes() : null; // determine range coercion types, these may not be the same QueryGraphValuePairRangeIndex rangeKeyIndexPair = queryGraphValue.getRangeProps(); String[] rangeIndexedProps = rangeKeyIndexPair.getIndexed(); CoercionDesc rangeCoercionTypeDesc = CoercionUtil.getCoercionTypesRange(typesPerStream, indexedStream, rangeIndexedProps, rangeKeyIndexPair.getKeys()); Class[] rangeCoercionType = rangeCoercionTypeDesc.isCoerce() ? rangeCoercionTypeDesc.getCoercionTypes() : null; // reduce to any unique index if applicable boolean unique = false; QueryPlanIndexUniqueHelper.ReducedHashKeys reduced = QueryPlanIndexUniqueHelper.reduceToUniqueIfPossible(hashIndexProps, hashCoercionTypes, hashKeyFunctions, indexStreamsUniqueProps); if (reduced != null) { hashIndexProps = reduced.getPropertyNames(); hashCoercionTypes = reduced.getCoercionTypes(); hashKeyFunctions = reduced.getHashKeyFunctions(); unique = true; rangeIndexedProps = new String[0]; rangeCoercionType = new Class[0]; rangeKeyIndexPair = new QueryGraphValuePairRangeIndex(rangeIndexedProps, Collections.<QueryGraphValueEntryRange>emptyList()); } // build index description QueryPlanIndexItem indexItem = new QueryPlanIndexItem(hashIndexProps, hashCoercionTypes, rangeIndexedProps, rangeCoercionType, unique); QueryPlanIndex queryPlanIndex = QueryPlanIndex.makeIndex(indexItem); String indexName = queryPlanIndex.getFirstIndexNum(); // straight no-range case means direct index lookup TableLookupPlan lookupPlan; if (rangeKeyIndexPair.getKeys().isEmpty()) { if (hashKeyFunctions.size() == 1) { QueryGraphValueEntryHashKeyed first = hashKeyFunctions.get(0); lookupPlan = new IndexedTableLookupPlanSingle(lookupStream, indexedStream, indexName, first); } else { lookupPlan = new IndexedTableLookupPlanMulti(lookupStream, indexedStream, indexName, hashKeyFunctions); } } // we have ranges else { // stream zero-to-one if (hashKeyFunctions.isEmpty() && rangeKeyIndexPair.getKeys().size() == 1) { lookupPlan = new SortedTableLookupPlan(lookupStream, indexedStream, indexName, rangeKeyIndexPair.getKeys().get(0)); } else { lookupPlan = new CompositeTableLookupPlan(lookupStream, indexedStream, indexName, hashKeyFunctions, rangeKeyIndexPair.getKeys()); } } return new Pair<QueryPlanIndex, TableLookupPlan>(queryPlanIndex, lookupPlan); } }