/* *************************************************************************************** * Copyright (C) 2006 EsperTech, Inc. All rights reserved. * * http://www.espertech.com/esper * * 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.lookup; import com.espertech.esper.client.EventType; import com.espertech.esper.collection.Pair; import com.espertech.esper.epl.expression.core.ExprNode; import com.espertech.esper.epl.expression.core.ExprNodeUtility; import com.espertech.esper.epl.index.quadtree.SubordTableLookupStrategyFactoryQuadTree; import com.espertech.esper.epl.index.service.EventAdvancedIndexProvisionDesc; import com.espertech.esper.epl.join.hint.ExcludePlanHint; import com.espertech.esper.epl.join.hint.IndexHint; import com.espertech.esper.epl.join.hint.IndexHintInstruction; import com.espertech.esper.epl.join.plan.*; import java.lang.annotation.Annotation; import java.util.*; public class SubordinateQueryPlanner { public static SubordinateWMatchExprQueryPlanResult planOnExpression( ExprNode joinExpr, EventType filterEventType, IndexHint optionalIndexHint, boolean isIndexShare, int subqueryNumber, ExcludePlanHint excludePlanHint, boolean isVirtualDataWindow, EventTableIndexMetadata indexMetadata, EventType eventTypeIndexed, Set<String> optionalUniqueKeyProps, boolean onlyUseExistingIndexes, String statementName, int statementId, Annotation[] annotations) { EventType[] allStreamsZeroIndexed = new EventType[]{eventTypeIndexed, filterEventType}; EventType[] outerStreams = new EventType[]{filterEventType}; SubordPropPlan joinedPropPlan = QueryPlanIndexBuilder.getJoinProps(joinExpr, 1, allStreamsZeroIndexed, excludePlanHint); // No join expression means all if (joinExpr == null && !isVirtualDataWindow) { return new SubordinateWMatchExprQueryPlanResult(new SubordWMatchExprLookupStrategyFactoryAllUnfiltered(), null); } SubordinateQueryPlanDesc queryPlanDesc = planSubquery(outerStreams, joinedPropPlan, true, false, optionalIndexHint, isIndexShare, subqueryNumber, isVirtualDataWindow, indexMetadata, optionalUniqueKeyProps, onlyUseExistingIndexes, statementName, statementId, annotations); if (queryPlanDesc == null) { return new SubordinateWMatchExprQueryPlanResult(new SubordWMatchExprLookupStrategyFactoryAllFiltered(joinExpr.getExprEvaluator()), null); } if (joinExpr == null) { // it can be null when using virtual data window return new SubordinateWMatchExprQueryPlanResult( new SubordWMatchExprLookupStrategyFactoryIndexedUnfiltered(queryPlanDesc.getLookupStrategyFactory()), queryPlanDesc.getIndexDescs()); } else { return new SubordinateWMatchExprQueryPlanResult( new SubordWMatchExprLookupStrategyFactoryIndexedFiltered(joinExpr.getExprEvaluator(), queryPlanDesc.getLookupStrategyFactory()), queryPlanDesc.getIndexDescs()); } } public static SubordinateQueryPlanDesc planSubquery(EventType[] outerStreams, SubordPropPlan joinDesc, boolean isNWOnTrigger, boolean forceTableScan, IndexHint optionalIndexHint, boolean indexShare, int subqueryNumber, boolean isVirtualDataWindow, EventTableIndexMetadata indexMetadata, Set<String> optionalUniqueKeyProps, boolean onlyUseExistingIndexes, String statementName, int statementId, Annotation[] annotations) { if (isVirtualDataWindow) { SubordinateQueryPlannerIndexPropDesc indexProps = getIndexPropDesc(joinDesc.getHashProps(), joinDesc.getRangeProps()); SubordTableLookupStrategyFactoryVDW lookupStrategyFactory = new SubordTableLookupStrategyFactoryVDW(statementName, statementId, annotations, outerStreams, Arrays.asList(indexProps.getHashJoinedProps()), new CoercionDesc(false, indexProps.getHashIndexCoercionType()), Arrays.asList(indexProps.getRangeJoinedProps()), new CoercionDesc(false, indexProps.getRangeIndexCoercionType()), isNWOnTrigger, joinDesc, forceTableScan, indexProps.getListPair()); return new SubordinateQueryPlanDesc(lookupStrategyFactory, null); } if (joinDesc.getCustomIndexOps() != null && !joinDesc.getCustomIndexOps().isEmpty()) { for (Map.Entry<QueryGraphValueEntryCustomKey, QueryGraphValueEntryCustomOperation> op : joinDesc.getCustomIndexOps().entrySet()) { for (Map.Entry<IndexMultiKey, EventTableIndexMetadataEntry> index : indexMetadata.getIndexes().entrySet()) { if (isCustomIndexMatch(index, op)) { EventAdvancedIndexProvisionDesc provisionDesc = index.getValue().getQueryPlanIndexItem().getAdvancedIndexProvisionDesc(); SubordTableLookupStrategyFactoryQuadTree lookupStrategyFactory = provisionDesc.getFactory().getSubordinateLookupStrategy(op.getKey().getOperationName(), op.getValue().getPositionalExpressions(), isNWOnTrigger, outerStreams.length); SubordinateQueryIndexDesc indexDesc = new SubordinateQueryIndexDesc(null, index.getValue().getOptionalIndexName(), index.getKey(), null); return new SubordinateQueryPlanDesc(lookupStrategyFactory, new SubordinateQueryIndexDesc[] {indexDesc}); } } } } List<SubordPropHashKey> hashKeys = Collections.emptyList(); CoercionDesc hashKeyCoercionTypes = null; List<SubordPropRangeKey> rangeKeys = Collections.emptyList(); CoercionDesc rangeKeyCoercionTypes = null; ExprNode[] inKeywordSingleIdxKeys = null; ExprNode inKeywordMultiIdxKey = null; SubordinateQueryIndexDesc[] indexDescs; if (joinDesc.getInKeywordSingleIndex() != null) { SubordPropInKeywordSingleIndex single = joinDesc.getInKeywordSingleIndex(); SubordPropHashKey keyInfo = new SubordPropHashKey(new QueryGraphValueEntryHashKeyedExpr(single.getExpressions()[0], false), null, single.getCoercionType()); SubordinateQueryIndexDesc indexDesc = findOrSuggestIndex( Collections.singletonMap(single.getIndexedProp(), keyInfo), Collections.<String, SubordPropRangeKey>emptyMap(), optionalIndexHint, indexShare, subqueryNumber, indexMetadata, optionalUniqueKeyProps, onlyUseExistingIndexes); if (indexDesc == null) { return null; } SubordinateQueryIndexDesc desc = new SubordinateQueryIndexDesc(indexDesc.getOptionalIndexKeyInfo(), indexDesc.getIndexName(), indexDesc.getIndexMultiKey(), indexDesc.getQueryPlanIndexItem()); indexDescs = new SubordinateQueryIndexDesc[]{desc}; inKeywordSingleIdxKeys = single.getExpressions(); } else if (joinDesc.getInKeywordMultiIndex() != null) { SubordPropInKeywordMultiIndex multi = joinDesc.getInKeywordMultiIndex(); indexDescs = new SubordinateQueryIndexDesc[multi.getIndexedProp().length]; for (int i = 0; i < multi.getIndexedProp().length; i++) { SubordPropHashKey keyInfo = new SubordPropHashKey(new QueryGraphValueEntryHashKeyedExpr(multi.getExpression(), false), null, multi.getCoercionType()); SubordinateQueryIndexDesc indexDesc = findOrSuggestIndex( Collections.singletonMap(multi.getIndexedProp()[i], keyInfo), Collections.<String, SubordPropRangeKey>emptyMap(), optionalIndexHint, indexShare, subqueryNumber, indexMetadata, optionalUniqueKeyProps, onlyUseExistingIndexes); if (indexDesc == null) { return null; } indexDescs[i] = indexDesc; } inKeywordMultiIdxKey = multi.getExpression(); } else { SubordinateQueryIndexDesc indexDesc = findOrSuggestIndex(joinDesc.getHashProps(), joinDesc.getRangeProps(), optionalIndexHint, false, subqueryNumber, indexMetadata, optionalUniqueKeyProps, onlyUseExistingIndexes); if (indexDesc == null) { return null; } IndexKeyInfo indexKeyInfo = indexDesc.getOptionalIndexKeyInfo(); hashKeys = indexKeyInfo.getOrderedHashDesc(); hashKeyCoercionTypes = indexKeyInfo.getOrderedKeyCoercionTypes(); rangeKeys = indexKeyInfo.getOrderedRangeDesc(); rangeKeyCoercionTypes = indexKeyInfo.getOrderedRangeCoercionTypes(); SubordinateQueryIndexDesc desc = new SubordinateQueryIndexDesc(indexDesc.getOptionalIndexKeyInfo(), indexDesc.getIndexName(), indexDesc.getIndexMultiKey(), indexDesc.getQueryPlanIndexItem()); indexDescs = new SubordinateQueryIndexDesc[]{desc}; } if (forceTableScan) { return null; } SubordTableLookupStrategyFactory lookupStrategyFactory = SubordinateTableLookupStrategyUtil.getLookupStrategy(outerStreams, hashKeys, hashKeyCoercionTypes, rangeKeys, rangeKeyCoercionTypes, inKeywordSingleIdxKeys, inKeywordMultiIdxKey, isNWOnTrigger); return new SubordinateQueryPlanDesc(lookupStrategyFactory, indexDescs); } private static boolean isCustomIndexMatch(Map.Entry<IndexMultiKey, EventTableIndexMetadataEntry> index, Map.Entry<QueryGraphValueEntryCustomKey, QueryGraphValueEntryCustomOperation> op) { if (index.getValue().getExplicitIndexNameIfExplicit() == null || index.getValue().getQueryPlanIndexItem() == null) { return false; } EventAdvancedIndexProvisionDesc provision = index.getValue().getQueryPlanIndexItem().getAdvancedIndexProvisionDesc(); if (provision == null) { return false; } if (!provision.getFactory().providesIndexForOperation(op.getKey().getOperationName(), op.getValue().getPositionalExpressions())) { return false; } return ExprNodeUtility.deepEquals(index.getKey().getAdvancedIndexDesc().getIndexedExpressions(), op.getKey().getExprNodes(), true); } private static SubordinateQueryIndexDesc findOrSuggestIndex( Map<String, SubordPropHashKey> hashProps, Map<String, SubordPropRangeKey> rangeProps, IndexHint optionalIndexHint, boolean isIndexShare, int subqueryNumber, EventTableIndexMetadata indexMetadata, Set<String> optionalUniqueKeyProps, boolean onlyUseExistingIndexes) { SubordinateQueryPlannerIndexPropDesc indexProps = getIndexPropDesc(hashProps, rangeProps); SubordinateQueryPlannerIndexPropListPair hashedAndBtreeProps = indexProps.getListPair(); // Get or create the table for this index (exact match or property names, type of index and coercion type is expected) IndexKeyInfo indexKeyInfo; // how needs all of IndexKeyInfo+QueryPlanIndexItem+IndexMultiKey IndexMultiKey indexMultiKey; String indexName = null; QueryPlanIndexItem planIndexItem = null; if (hashedAndBtreeProps.getHashedProps().isEmpty() && hashedAndBtreeProps.getBtreeProps().isEmpty()) { return null; } Pair<IndexMultiKey, String> existing = null; Pair<QueryPlanIndexItem, IndexMultiKey> planned = null; // consider index hints List<IndexHintInstruction> optionalIndexHintInstructions = null; if (optionalIndexHint != null) { optionalIndexHintInstructions = optionalIndexHint.getInstructionsSubquery(subqueryNumber); } IndexMultiKey indexFoundPair = EventTableIndexUtil.findIndexConsiderTyping(indexMetadata.getIndexes(), hashedAndBtreeProps.getHashedProps(), hashedAndBtreeProps.getBtreeProps(), optionalIndexHintInstructions); if (indexFoundPair != null) { EventTableIndexMetadataEntry hintIndex = indexMetadata.getIndexes().get(indexFoundPair); existing = new Pair<IndexMultiKey, String>(indexFoundPair, hintIndex.getOptionalIndexName()); } // nothing found: plan one if (existing == null && !onlyUseExistingIndexes) { // not found, see if the item is declared unique List<IndexedPropDesc> proposedHashedProps = hashedAndBtreeProps.getHashedProps(); List<IndexedPropDesc> proposedBtreeProps = hashedAndBtreeProps.getBtreeProps(); // match against unique-key properties when suggesting an index boolean unique = false; boolean coerce = !isIndexShare; if (optionalUniqueKeyProps != null && !optionalUniqueKeyProps.isEmpty()) { List<IndexedPropDesc> newHashProps = new ArrayList<IndexedPropDesc>(); for (String uniqueKey : optionalUniqueKeyProps) { boolean found = false; for (IndexedPropDesc hashProp : hashedAndBtreeProps.getHashedProps()) { if (hashProp.getIndexPropName().equals(uniqueKey)) { newHashProps.add(hashProp); found = true; break; } } if (!found) { newHashProps = null; break; } } if (newHashProps != null) { proposedHashedProps = newHashProps; proposedBtreeProps = Collections.emptyList(); unique = true; coerce = false; } } planned = planIndex(unique, proposedHashedProps, proposedBtreeProps, coerce); } // compile index information if (existing == null && planned == null) { return null; } // handle existing if (existing != null) { indexKeyInfo = SubordinateQueryPlannerUtil.compileIndexKeyInfo(existing.getFirst(), indexProps.getHashIndexPropsProvided(), indexProps.getHashJoinedProps(), indexProps.getRangeIndexPropsProvided(), indexProps.getRangeJoinedProps()); indexName = existing.getSecond(); indexMultiKey = existing.getFirst(); } else { // handle planned indexKeyInfo = SubordinateQueryPlannerUtil.compileIndexKeyInfo(planned.getSecond(), indexProps.getHashIndexPropsProvided(), indexProps.getHashJoinedProps(), indexProps.getRangeIndexPropsProvided(), indexProps.getRangeJoinedProps()); indexMultiKey = planned.getSecond(); planIndexItem = planned.getFirst(); } return new SubordinateQueryIndexDesc(indexKeyInfo, indexName, indexMultiKey, planIndexItem); } private static SubordinateQueryPlannerIndexPropDesc getIndexPropDesc(Map<String, SubordPropHashKey> hashProps, Map<String, SubordPropRangeKey> rangeProps) { // hash property names and types String[] hashIndexPropsProvided = new String[hashProps.size()]; Class[] hashIndexCoercionType = new Class[hashProps.size()]; SubordPropHashKey[] hashJoinedProps = new SubordPropHashKey[hashProps.size()]; int count = 0; for (Map.Entry<String, SubordPropHashKey> entry : hashProps.entrySet()) { hashIndexPropsProvided[count] = entry.getKey(); hashIndexCoercionType[count] = entry.getValue().getCoercionType(); hashJoinedProps[count++] = entry.getValue(); } // range property names and types String[] rangeIndexPropsProvided = new String[rangeProps.size()]; Class[] rangeIndexCoercionType = new Class[rangeProps.size()]; SubordPropRangeKey[] rangeJoinedProps = new SubordPropRangeKey[rangeProps.size()]; count = 0; for (Map.Entry<String, SubordPropRangeKey> entry : rangeProps.entrySet()) { rangeIndexPropsProvided[count] = entry.getKey(); rangeIndexCoercionType[count] = entry.getValue().getCoercionType(); rangeJoinedProps[count++] = entry.getValue(); } // Add all joined fields to an array for sorting SubordinateQueryPlannerIndexPropListPair listPair = SubordinateQueryPlannerUtil.toListOfHashedAndBtreeProps(hashIndexPropsProvided, hashIndexCoercionType, rangeIndexPropsProvided, rangeIndexCoercionType); return new SubordinateQueryPlannerIndexPropDesc(hashIndexPropsProvided, hashIndexCoercionType, rangeIndexPropsProvided, rangeIndexCoercionType, listPair, hashJoinedProps, rangeJoinedProps); } private static Pair<QueryPlanIndexItem, IndexMultiKey> planIndex(boolean unique, List<IndexedPropDesc> hashProps, List<IndexedPropDesc> btreeProps, boolean mustCoerce) { // not resolved as full match and not resolved as unique index match, allocate IndexMultiKey indexPropKey = new IndexMultiKey(unique, hashProps, btreeProps, null); IndexedPropDesc[] indexedPropDescs = hashProps.toArray(new IndexedPropDesc[hashProps.size()]); String[] indexProps = IndexedPropDesc.getIndexProperties(indexedPropDescs); Class[] indexCoercionTypes = IndexedPropDesc.getCoercionTypes(indexedPropDescs); if (!mustCoerce) { indexCoercionTypes = null; } IndexedPropDesc[] rangePropDescs = btreeProps.toArray(new IndexedPropDesc[btreeProps.size()]); String[] rangeProps = IndexedPropDesc.getIndexProperties(rangePropDescs); Class[] rangeCoercionTypes = IndexedPropDesc.getCoercionTypes(rangePropDescs); QueryPlanIndexItem indexItem = new QueryPlanIndexItem(indexProps, indexCoercionTypes, rangeProps, rangeCoercionTypes, unique, null); return new Pair<QueryPlanIndexItem, IndexMultiKey>(indexItem, indexPropKey); } }