/************************************************************************************** * 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.epl.expression.ExprNode; import com.espertech.esper.epl.lookup.SubordPropHashKey; import com.espertech.esper.epl.lookup.SubordPropPlan; import com.espertech.esper.epl.lookup.SubordPropRangeKey; import com.espertech.esper.util.JavaClassHelper; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; /** * Build query index plans. */ public class QueryPlanIndexBuilder { /** * Build index specification from navigability info. * <p> * Looks at each stream and determines which properties in the stream must be indexed * in order for other streams to look up into the stream. Determines the unique set of properties * to avoid building duplicate indexes on the same set of properties. * @param queryGraph - navigability info * @return query index specs for each stream */ public static QueryPlanIndex[] buildIndexSpec(QueryGraph queryGraph, EventType[] typePerStream, String[][][] indexedStreamsUniqueProps) { int numStreams = queryGraph.getNumStreams(); QueryPlanIndex[] indexSpecs = new QueryPlanIndex[numStreams]; // For each stream compile a list of index property sets. for (int streamIndexed = 0; streamIndexed < numStreams; streamIndexed++) { List<QueryPlanIndexItem> indexesSet = new ArrayList<QueryPlanIndexItem>(); // Look at the index from the viewpoint of the stream looking up in the index for (int streamLookup = 0; streamLookup < numStreams; streamLookup++) { if (streamIndexed == streamLookup) { continue; } QueryGraphValue value = queryGraph.getGraphValue(streamLookup, streamIndexed); QueryGraphValuePairHashKeyIndex hashKeyAndIndexProps = value.getHashKeyProps(); // Sort index properties, but use the sorted properties only to eliminate duplicates String[] hashIndexProps = hashKeyAndIndexProps.getIndexed(); List<QueryGraphValueEntryHashKeyed> hashKeyProps = hashKeyAndIndexProps.getKeys(); CoercionDesc indexCoercionTypes = CoercionUtil.getCoercionTypesHash(typePerStream, streamLookup, streamIndexed, hashKeyProps, hashIndexProps); Class[] hashCoercionTypeArr = indexCoercionTypes.getCoercionTypes(); QueryGraphValuePairRangeIndex rangeAndIndexProps = value.getRangeProps(); String[] rangeIndexProps = rangeAndIndexProps.getIndexed(); List<QueryGraphValueEntryRange> rangeKeyProps = rangeAndIndexProps.getKeys(); CoercionDesc rangeCoercionTypes = CoercionUtil.getCoercionTypesRange(typePerStream, streamIndexed, rangeIndexProps, rangeKeyProps); Class[] rangeCoercionTypeArr = rangeCoercionTypes.getCoercionTypes(); if (hashIndexProps.length == 0 && rangeIndexProps.length == 0) { continue; } // reduce to any unique index if applicable boolean unique = false; QueryPlanIndexUniqueHelper.ReducedHashKeys reduced = QueryPlanIndexUniqueHelper.reduceToUniqueIfPossible(hashIndexProps, hashCoercionTypeArr, hashKeyProps, indexedStreamsUniqueProps[streamIndexed]); if (reduced != null) { hashIndexProps = reduced.getPropertyNames(); hashCoercionTypeArr = reduced.getCoercionTypes(); unique = true; rangeIndexProps = new String[0]; rangeCoercionTypeArr = new Class[0]; } QueryPlanIndexItem proposed = new QueryPlanIndexItem(hashIndexProps, hashCoercionTypeArr, rangeIndexProps, rangeCoercionTypeArr, unique); boolean found = false; for (QueryPlanIndexItem index : indexesSet) { if (proposed.equalsCompareSortedProps(index)) { found = true; break; } } if (!found) { indexesSet.add(proposed); } } // Copy the index properties for the stream to a QueryPlanIndex instance if (indexesSet.isEmpty()) { indexesSet.add(new QueryPlanIndexItem(null, null, null, null, false)); } indexSpecs[streamIndexed] = QueryPlanIndex.makeIndex(indexesSet); } return indexSpecs; } public static SubordPropPlan getJoinProps(ExprNode filterExpr, int outsideStreamCount, EventType[] allStreamTypesZeroIndexed) { // No filter expression means full table scan if (filterExpr == null) { return new SubordPropPlan(); } // analyze query graph QueryGraph queryGraph = new QueryGraph(outsideStreamCount + 1); FilterExprAnalyzer.analyze(filterExpr, queryGraph, false); // Build a list of streams and indexes LinkedHashMap<String, SubordPropHashKey> joinProps = new LinkedHashMap<String, SubordPropHashKey>(); LinkedHashMap<String, SubordPropRangeKey> rangeProps = new LinkedHashMap<String, SubordPropRangeKey>(); for (int stream = 0; stream < outsideStreamCount; stream++) { int lookupStream = stream + 1; QueryGraphValue queryGraphValue = queryGraph.getGraphValue(lookupStream, 0); QueryGraphValuePairHashKeyIndex hashKeysAndIndexes = queryGraphValue.getHashKeyProps(); // handle key-lookups List<QueryGraphValueEntryHashKeyed> keyPropertiesJoin = hashKeysAndIndexes.getKeys(); String[] indexPropertiesJoin = hashKeysAndIndexes.getIndexed(); if (!keyPropertiesJoin.isEmpty()) { if (keyPropertiesJoin.size() != indexPropertiesJoin.length) { throw new IllegalStateException("Invalid query key and index property collection for stream " + stream); } for (int i = 0; i < keyPropertiesJoin.size(); i++) { QueryGraphValueEntryHashKeyed keyDesc = keyPropertiesJoin.get(i); ExprNode compareNode = keyDesc.getKeyExpr(); Class keyPropType = JavaClassHelper.getBoxedType(compareNode.getExprEvaluator().getType()); Class indexedPropType = JavaClassHelper.getBoxedType(allStreamTypesZeroIndexed[0].getPropertyType(indexPropertiesJoin[i])); Class coercionType = indexedPropType; if (keyPropType != indexedPropType) { coercionType = JavaClassHelper.getCompareToCoercionType(keyPropType, indexedPropType); } SubordPropHashKey desc; if (keyPropertiesJoin.get(i) instanceof QueryGraphValueEntryHashKeyedExpr) { QueryGraphValueEntryHashKeyedExpr keyExpr = (QueryGraphValueEntryHashKeyedExpr) keyPropertiesJoin.get(i); Integer keyStreamNum = keyExpr.isRequiresKey() ? stream : null; desc = new SubordPropHashKey(keyDesc, keyStreamNum, coercionType); } else { QueryGraphValueEntryHashKeyedProp prop = (QueryGraphValueEntryHashKeyedProp) keyDesc; desc = new SubordPropHashKey(prop, stream, coercionType); } joinProps.put(indexPropertiesJoin[i], desc); } } // handle range lookups QueryGraphValuePairRangeIndex rangeKeysAndIndexes = queryGraphValue.getRangeProps(); String[] rangeIndexes = rangeKeysAndIndexes.getIndexed(); List<QueryGraphValueEntryRange> rangeDescs = rangeKeysAndIndexes.getKeys(); if (rangeDescs.isEmpty()) { continue; } // get all ranges lookups int count = -1; for (QueryGraphValueEntryRange rangeDesc : rangeDescs) { count++; String rangeIndexProp = rangeIndexes[count]; SubordPropRangeKey subqRangeDesc = rangeProps.get(rangeIndexProp); // other streams may specify the start or end endpoint of a range, therefore this operation can be additive if (subqRangeDesc != null) { if (subqRangeDesc.getRangeInfo().getType().isRange()) { continue; } // see if we can make this additive by using a range QueryGraphValueEntryRangeRelOp relOpOther = (QueryGraphValueEntryRangeRelOp) subqRangeDesc.getRangeInfo(); QueryGraphValueEntryRangeRelOp relOpThis = (QueryGraphValueEntryRangeRelOp) rangeDesc; QueryGraphRangeConsolidateDesc opsDesc = QueryGraphRangeUtil.getCanConsolidate(relOpThis.getType(), relOpOther.getType()); if (opsDesc != null) { ExprNode start; ExprNode end; int streamNumStart; int streamNumEnd; if (!opsDesc.isReverse()) { start = relOpOther.getExpression(); end = relOpThis.getExpression(); streamNumEnd = stream; } else { start = relOpThis.getExpression(); streamNumStart = stream; end = relOpOther.getExpression(); } boolean allowRangeReversal = relOpOther.isBetweenPart() && relOpThis.isBetweenPart(); QueryGraphValueEntryRangeIn rangeIn = new QueryGraphValueEntryRangeIn(opsDesc.getType(), start, end, allowRangeReversal); Class indexedPropType = JavaClassHelper.getBoxedType(allStreamTypesZeroIndexed[0].getPropertyType(rangeIndexProp)); Class coercionType = indexedPropType; Class proposedType = CoercionUtil.getCoercionTypeRangeIn(indexedPropType, rangeIn.getExprStart(), rangeIn.getExprEnd()); if (proposedType != null && proposedType != indexedPropType) { coercionType = proposedType; } subqRangeDesc = new SubordPropRangeKey(rangeIn, coercionType); rangeProps.put(rangeIndexProp, subqRangeDesc); } // ignore continue; } // an existing entry has not been found if (rangeDesc.getType().isRange()) { QueryGraphValueEntryRangeIn rangeIn = (QueryGraphValueEntryRangeIn) rangeDesc; Class indexedPropType = JavaClassHelper.getBoxedType(allStreamTypesZeroIndexed[0].getPropertyType(rangeIndexProp)); Class coercionType = indexedPropType; Class proposedType = CoercionUtil.getCoercionTypeRangeIn(indexedPropType, rangeIn.getExprStart(), rangeIn.getExprEnd()); if (proposedType != null && proposedType != indexedPropType) { coercionType = proposedType; } subqRangeDesc = new SubordPropRangeKey(rangeDesc, coercionType); } else { QueryGraphValueEntryRangeRelOp relOp = (QueryGraphValueEntryRangeRelOp) rangeDesc; Class keyPropType = relOp.getExpression().getExprEvaluator().getType(); Class indexedPropType = JavaClassHelper.getBoxedType(allStreamTypesZeroIndexed[0].getPropertyType(rangeIndexProp)); Class coercionType = indexedPropType; if (keyPropType != indexedPropType) { coercionType = JavaClassHelper.getCompareToCoercionType(keyPropType, indexedPropType); } subqRangeDesc = new SubordPropRangeKey(rangeDesc, coercionType); } rangeProps.put(rangeIndexProp, subqRangeDesc); } } return new SubordPropPlan(joinProps, rangeProps); } }