/*
***************************************************************************************
* 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.EPException;
import com.espertech.esper.client.EventType;
import com.espertech.esper.collection.Pair;
import com.espertech.esper.core.service.ExprEvaluatorContextStatement;
import com.espertech.esper.core.service.StatementContext;
import com.espertech.esper.epl.core.EngineImportException;
import com.espertech.esper.epl.core.StreamTypeService;
import com.espertech.esper.epl.core.StreamTypeServiceImpl;
import com.espertech.esper.epl.expression.core.*;
import com.espertech.esper.epl.expression.visitor.ExprNodeIdentifierAndStreamRefVisitor;
import com.espertech.esper.epl.index.service.AdvancedIndexFactoryProvider;
import com.espertech.esper.epl.index.service.EventAdvancedIndexProvisionDesc;
import com.espertech.esper.epl.join.hint.IndexHintInstruction;
import com.espertech.esper.epl.join.hint.IndexHintInstructionBust;
import com.espertech.esper.epl.join.hint.IndexHintInstructionExplicit;
import com.espertech.esper.epl.join.hint.IndexHintInstructionIndexName;
import com.espertech.esper.epl.join.plan.QueryPlanIndexItem;
import com.espertech.esper.epl.spec.CreateIndexItem;
import com.espertech.esper.epl.spec.CreateIndexType;
import com.espertech.esper.util.JavaClassHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.*;
public class EventTableIndexUtil {
private static final Logger log = LoggerFactory.getLogger(EventTableIndexUtil.class);
private final static IndexComparatorShortestPath INDEX_COMPARATOR_INSTANCE = new IndexComparatorShortestPath();
public static QueryPlanIndexItem validateCompileExplicitIndex(String indexName, boolean unique, List<CreateIndexItem> columns, EventType eventType, StatementContext statementContext)
throws ExprValidationException {
List<IndexedPropDesc> hashProps = new ArrayList<IndexedPropDesc>();
List<IndexedPropDesc> btreeProps = new ArrayList<IndexedPropDesc>();
Set<String> indexedColumns = new HashSet<String>();
EventAdvancedIndexProvisionDesc advancedIndexProvisionDesc = null;
for (CreateIndexItem columnDesc : columns) {
String indexType = columnDesc.getType().toLowerCase(Locale.ENGLISH).trim();
if (indexType.equals(CreateIndexType.HASH.getNameLower()) || indexType.equals(CreateIndexType.BTREE.getNameLower())) {
validateBuiltin(columnDesc, eventType, hashProps, btreeProps, indexedColumns);
} else {
if (advancedIndexProvisionDesc != null) {
throw new ExprValidationException("Nested advanced-type indexes are not supported");
}
advancedIndexProvisionDesc = validateAdvanced(indexName, indexType, columnDesc, eventType, statementContext);
}
}
if (unique && !btreeProps.isEmpty()) {
throw new ExprValidationException("Combination of unique index with btree (range) is not supported");
}
if ((!btreeProps.isEmpty() || !hashProps.isEmpty()) && advancedIndexProvisionDesc != null) {
throw new ExprValidationException("Combination of hash/btree columns an advanced-type indexes is not supported");
}
return new QueryPlanIndexItem(hashProps, btreeProps, unique, advancedIndexProvisionDesc);
}
private static EventAdvancedIndexProvisionDesc validateAdvanced(String indexName, String indexType, CreateIndexItem columnDesc, EventType eventType, StatementContext statementContext) throws ExprValidationException {
// validate index expressions: valid and plain expressions
ExprValidationContext validationContextColumns = getValidationContext(eventType, statementContext);
ExprNode[] columns = columnDesc.getExpressions().toArray(new ExprNode[columnDesc.getExpressions().size()]);
ExprNodeUtility.getValidatedSubtree(ExprNodeOrigin.CREATEINDEXCOLUMN, columns, validationContextColumns);
ExprNodeUtility.validatePlainExpression(ExprNodeOrigin.CREATEINDEXCOLUMN, columns);
// validate parameters, may not depend on props
ExprNode[] parameters = null;
if (columnDesc.getParameters() != null && !columnDesc.getParameters().isEmpty()) {
parameters = columnDesc.getParameters().toArray(new ExprNode[columnDesc.getParameters().size()]);
ExprNodeUtility.getValidatedSubtree(ExprNodeOrigin.CREATEINDEXPARAMETER, parameters, validationContextColumns);
ExprNodeUtility.validatePlainExpression(ExprNodeOrigin.CREATEINDEXPARAMETER, parameters);
// validate no stream dependency of parameters
ExprNodeIdentifierAndStreamRefVisitor visitor = new ExprNodeIdentifierAndStreamRefVisitor(false);
for (ExprNode param : columnDesc.getParameters()) {
param.accept(visitor);
if (!visitor.getRefs().isEmpty()) {
throw new ExprValidationException("Index parameters may not refer to event properties");
}
}
}
// obtain provider
AdvancedIndexFactoryProvider provider;
try {
provider = statementContext.getEngineImportService().resolveAdvancedIndexProvider(indexType);
} catch (EngineImportException ex) {
throw new ExprValidationException(ex.getMessage(), ex);
}
return provider.validateEventIndex(indexName, indexType, columns, parameters);
}
private static void validateBuiltin(CreateIndexItem columnDesc, EventType eventType, List<IndexedPropDesc> hashProps, List<IndexedPropDesc> btreeProps, Set<String> indexedColumns)
throws ExprValidationException {
if (columnDesc.getExpressions().isEmpty()) {
throw new ExprValidationException("Invalid empty list of index expressions");
}
if (columnDesc.getExpressions().size() > 1) {
throw new ExprValidationException("Invalid multiple index expressions for index type '" + columnDesc.getType() + "'");
}
ExprNode expression = columnDesc.getExpressions().get(0);
if (!(expression instanceof ExprIdentNode)) {
throw new ExprValidationException("Invalid index expression '" + ExprNodeUtility.toExpressionStringMinPrecedenceSafe(expression) + "'");
}
ExprIdentNode identNode = (ExprIdentNode) expression;
if (identNode.getFullUnresolvedName().contains(".")) {
throw new ExprValidationException("Invalid index expression '" + ExprNodeUtility.toExpressionStringMinPrecedenceSafe(expression) + "'");
}
String columnName = identNode.getFullUnresolvedName();
Class type = JavaClassHelper.getBoxedType(eventType.getPropertyType(columnName));
if (type == null) {
throw new ExprValidationException("Property named '" + columnName + "' not found");
}
if (!indexedColumns.add(columnName)) {
throw new ExprValidationException("Property named '" + columnName + "' has been declared more then once");
}
IndexedPropDesc desc = new IndexedPropDesc(columnName, type);
String indexType = columnDesc.getType().toLowerCase(Locale.ENGLISH);
if (indexType.equals(CreateIndexType.HASH.getNameLower())) {
hashProps.add(desc);
} else {
btreeProps.add(desc);
}
}
public static IndexMultiKey findIndexConsiderTyping(Map<IndexMultiKey, EventTableIndexMetadataEntry> tableIndexesRefCount,
List<IndexedPropDesc> hashProps,
List<IndexedPropDesc> btreeProps,
List<IndexHintInstruction> optionalIndexHintInstructions) {
if (hashProps.isEmpty() && btreeProps.isEmpty()) {
throw new IllegalArgumentException("Invalid zero element list for hash and btree columns");
}
Map<IndexMultiKey, EventTableIndexRepositoryEntry> indexCandidates = (Map<IndexMultiKey, EventTableIndexRepositoryEntry>) EventTableIndexUtil.findCandidates(tableIndexesRefCount, hashProps, btreeProps);
// if there are hints, follow these
if (optionalIndexHintInstructions != null) {
IndexMultiKey found = EventTableIndexUtil.findByIndexHint(indexCandidates, optionalIndexHintInstructions);
if (found != null) {
return found;
}
}
// Get an existing table, if any, matching the exact requirement, prefer unique
IndexMultiKey indexPropKeyMatch = EventTableIndexUtil.findExactMatchNameAndType(tableIndexesRefCount.keySet(), true, hashProps, btreeProps, null);
if (indexPropKeyMatch == null) {
indexPropKeyMatch = EventTableIndexUtil.findExactMatchNameAndType(tableIndexesRefCount.keySet(), false, hashProps, btreeProps, null);
}
if (indexPropKeyMatch != null) {
return indexPropKeyMatch;
}
if (indexCandidates.isEmpty()) {
return null;
}
return getBestCandidate((Map<IndexMultiKey, EventTableIndexEntryBase>) (Map) indexCandidates).getFirst();
}
public static Pair<IndexMultiKey, EventTableIndexEntryBase> findIndexBestAvailable(Map<IndexMultiKey, ? extends EventTableIndexEntryBase> tablesAvailable,
Set<String> keyPropertyNames,
Set<String> rangePropertyNames,
List<IndexHintInstruction> optionalIndexHintInstructions) {
if (keyPropertyNames.isEmpty() && rangePropertyNames.isEmpty()) {
return null;
}
// determine candidates
List<IndexedPropDesc> hashProps = new ArrayList<IndexedPropDesc>();
for (String keyPropertyName : keyPropertyNames) {
hashProps.add(new IndexedPropDesc(keyPropertyName, null));
}
List<IndexedPropDesc> rangeProps = new ArrayList<IndexedPropDesc>();
for (String rangePropertyName : rangePropertyNames) {
rangeProps.add(new IndexedPropDesc(rangePropertyName, null));
}
Map<IndexMultiKey, EventTableIndexEntryBase> indexCandidates = (Map<IndexMultiKey, EventTableIndexEntryBase>) EventTableIndexUtil.findCandidates(tablesAvailable, hashProps, rangeProps);
// handle hint
if (optionalIndexHintInstructions != null) {
IndexMultiKey found = EventTableIndexUtil.findByIndexHint(indexCandidates, optionalIndexHintInstructions);
if (found != null) {
return getPair(tablesAvailable, found);
}
}
// no candidates
if (indexCandidates == null || indexCandidates.isEmpty()) {
if (log.isDebugEnabled()) {
log.debug("No index found.");
}
return null;
}
return getBestCandidate(indexCandidates);
}
private static Pair<IndexMultiKey, EventTableIndexEntryBase> getBestCandidate(Map<IndexMultiKey, EventTableIndexEntryBase> indexCandidates) {
// take the table that has a unique index
List<IndexMultiKey> indexes = new ArrayList<IndexMultiKey>();
for (Map.Entry<IndexMultiKey, EventTableIndexEntryBase> entry : indexCandidates.entrySet()) {
if (entry.getKey().isUnique()) {
indexes.add(entry.getKey());
}
}
if (!indexes.isEmpty()) {
Collections.sort(indexes, INDEX_COMPARATOR_INSTANCE);
return getPair(indexCandidates, indexes.get(0));
}
// take the best available table
indexes.clear();
indexes.addAll(indexCandidates.keySet());
if (indexes.size() > 1) {
Collections.sort(indexes, INDEX_COMPARATOR_INSTANCE);
}
return getPair(indexCandidates, indexes.get(0));
}
public static IndexMultiKey findByIndexHint(Map<IndexMultiKey, ? extends EventTableIndexEntryBase> indexCandidates, List<IndexHintInstruction> instructions) {
for (IndexHintInstruction instruction : instructions) {
if (instruction instanceof IndexHintInstructionIndexName) {
String indexName = ((IndexHintInstructionIndexName) instruction).getIndexName();
IndexMultiKey found = findExplicitIndexByName(indexCandidates, indexName);
if (found != null) {
return found;
}
}
if (instruction instanceof IndexHintInstructionExplicit) {
IndexMultiKey found = findExplicitIndexAnyName(indexCandidates);
if (found != null) {
return found;
}
}
if (instruction instanceof IndexHintInstructionBust) {
throw new EPException("Failed to plan index access, index hint busted out");
}
}
return null;
}
public static IndexMultiKey findExactMatchNameAndType(Set<IndexMultiKey> indexMultiKeys, boolean unique, List<IndexedPropDesc> hashProps, List<IndexedPropDesc> btreeProps, AdvancedIndexDesc advancedIndexDesc) {
for (IndexMultiKey existing : indexMultiKeys) {
if (isExactMatch(existing, unique, hashProps, btreeProps, advancedIndexDesc)) {
return existing;
}
}
return null;
}
private static Map<IndexMultiKey, ? extends EventTableIndexEntryBase> findCandidates(Map<IndexMultiKey, ? extends EventTableIndexEntryBase> indexes, List<IndexedPropDesc> hashProps, List<IndexedPropDesc> btreeProps) {
Map<IndexMultiKey, EventTableIndexEntryBase> indexCandidates = new HashMap<IndexMultiKey, EventTableIndexEntryBase>();
for (Map.Entry<IndexMultiKey, ? extends EventTableIndexEntryBase> entry : indexes.entrySet()) {
if (entry.getKey().getAdvancedIndexDesc() != null) {
continue;
}
boolean matches = indexMatchesProvided(entry.getKey(), hashProps, btreeProps);
if (matches) {
indexCandidates.put(entry.getKey(), entry.getValue());
}
}
return indexCandidates;
}
private static IndexMultiKey findExplicitIndexByName(Map<IndexMultiKey, ? extends EventTableIndexEntryBase> indexCandidates, String name) {
for (Map.Entry<IndexMultiKey, ? extends EventTableIndexEntryBase> entry : indexCandidates.entrySet()) {
if (entry.getValue().getOptionalIndexName() != null && entry.getValue().getOptionalIndexName().equals(name)) {
return entry.getKey();
}
}
return null;
}
private static IndexMultiKey findExplicitIndexAnyName(Map<IndexMultiKey, ? extends EventTableIndexEntryBase> indexCandidates) {
for (Map.Entry<IndexMultiKey, ? extends EventTableIndexEntryBase> entry : indexCandidates.entrySet()) {
if (entry.getValue().getOptionalIndexName() != null) {
return entry.getKey();
}
}
return null;
}
private static boolean indexHashIsProvided(IndexedPropDesc hashPropIndexed, List<IndexedPropDesc> hashPropsProvided) {
for (IndexedPropDesc hashPropProvided : hashPropsProvided) {
boolean nameMatch = hashPropProvided.getIndexPropName().equals(hashPropIndexed.getIndexPropName());
boolean typeMatch = true;
if (hashPropProvided.getCoercionType() != null && !JavaClassHelper.isSubclassOrImplementsInterface(JavaClassHelper.getBoxedType(hashPropProvided.getCoercionType()), JavaClassHelper.getBoxedType(hashPropIndexed.getCoercionType()))) {
typeMatch = false;
}
if (nameMatch && typeMatch) {
return true;
}
}
return false;
}
private static boolean isExactMatch(IndexMultiKey existing, boolean unique, List<IndexedPropDesc> hashProps, List<IndexedPropDesc> btreeProps, AdvancedIndexDesc advancedIndexDesc) {
if (existing.isUnique() != unique) {
return false;
}
if (!IndexedPropDesc.compare(Arrays.asList(existing.getHashIndexedProps()), hashProps)) {
return false;
}
if (!IndexedPropDesc.compare(Arrays.asList(existing.getRangeIndexedProps()), btreeProps)) {
return false;
}
if (existing.getAdvancedIndexDesc() == null) {
return advancedIndexDesc == null;
}
return advancedIndexDesc != null && existing.getAdvancedIndexDesc().equalsAdvancedIndex(advancedIndexDesc);
}
private static boolean indexMatchesProvided(IndexMultiKey indexDesc, List<IndexedPropDesc> hashPropsProvided, List<IndexedPropDesc> rangePropsProvided) {
IndexedPropDesc[] hashPropIndexedList = indexDesc.getHashIndexedProps();
for (IndexedPropDesc hashPropIndexed : hashPropIndexedList) {
boolean foundHashProp = indexHashIsProvided(hashPropIndexed, hashPropsProvided);
if (!foundHashProp) {
return false;
}
}
IndexedPropDesc[] rangePropIndexedList = indexDesc.getRangeIndexedProps();
for (IndexedPropDesc rangePropIndexed : rangePropIndexedList) {
boolean foundRangeProp = indexHashIsProvided(rangePropIndexed, rangePropsProvided);
if (!foundRangeProp) {
return false;
}
}
return true;
}
private static Pair<IndexMultiKey, EventTableIndexEntryBase> getPair(Map<IndexMultiKey, ? extends EventTableIndexEntryBase> tableIndexesRefCount, IndexMultiKey indexMultiKey) {
EventTableIndexEntryBase indexFound = tableIndexesRefCount.get(indexMultiKey);
return new Pair<IndexMultiKey, EventTableIndexEntryBase>(indexMultiKey, indexFound);
}
private static ExprValidationContext getValidationContext(EventType eventType, StatementContext statementContext) {
StreamTypeService streamTypeService = new StreamTypeServiceImpl(eventType, null, false, statementContext.getEngineURI());
return new ExprValidationContext(streamTypeService, statementContext.getEngineImportService(),
statementContext.getStatementExtensionServicesContext(), null, statementContext.getTimeProvider(), statementContext.getVariableService(), statementContext.getTableService(), new ExprEvaluatorContextStatement(statementContext, false),
statementContext.getEventAdapterService(), statementContext.getStatementName(), statementContext.getStatementId(), statementContext.getAnnotations(), statementContext.getContextDescriptor(), true, false, false, false, null, false);
}
private static class IndexComparatorShortestPath implements Comparator<IndexMultiKey>, Serializable {
private static final long serialVersionUID = -2214412607714095566L;
public int compare(IndexMultiKey o1, IndexMultiKey o2) {
String[] indexedProps1 = IndexedPropDesc.getIndexProperties(o1.getHashIndexedProps());
String[] indexedProps2 = IndexedPropDesc.getIndexProperties(o2.getHashIndexedProps());
if (indexedProps1.length > indexedProps2.length) {
return 1; // sort desc by count columns
}
if (indexedProps1.length == indexedProps2.length) {
return 0;
}
return -1;
}
}
}