/**************************************************************************************
* 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.named;
import com.espertech.esper.client.EPException;
import com.espertech.esper.client.EventBean;
import com.espertech.esper.client.EventType;
import com.espertech.esper.collection.Pair;
import com.espertech.esper.epl.join.hint.*;
import com.espertech.esper.epl.join.plan.QueryPlanIndexItem;
import com.espertech.esper.epl.join.table.EventTable;
import com.espertech.esper.epl.join.table.EventTableAndNamePair;
import com.espertech.esper.epl.join.table.EventTableUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.*;
/**
* A repository of index tables for use with a single named window and all it's deleting statements that
* may use the indexes to correlate triggering events with indexed events of the named window.
* <p>
* Maintains index tables and keeps a reference count for user. Allows reuse of indexes for multiple
* deleting statements.
*/
public class NamedWindowIndexRepository
{
private static final Log log = LogFactory.getLog(NamedWindowIndexRepository.class);
private List<EventTable> tables;
private Map<IndexMultiKey, NamedWindowIndexRepEntry> tableIndexesRefCount;
/**
* Ctor.
*/
public NamedWindowIndexRepository()
{
tables = new ArrayList<EventTable>();
tableIndexesRefCount = new HashMap<IndexMultiKey, NamedWindowIndexRepEntry>();
}
public Pair<IndexMultiKey, EventTableAndNamePair> addExplicitIndexOrReuse(
boolean unique,
List<IndexedPropDesc> hashProps,
List<IndexedPropDesc> btreeProps,
Iterable<EventBean> prefilledEvents,
EventType indexedType,
String indexName)
{
if (hashProps.isEmpty() && btreeProps.isEmpty()) {
throw new IllegalArgumentException("Invalid zero element list for hash and btree columns");
}
// Get an existing table, if any, matching the exact requirement
IndexMultiKey indexPropKeyMatch = findExactMatchNameAndType(tableIndexesRefCount.keySet(), unique, hashProps, btreeProps);
if (indexPropKeyMatch != null) {
NamedWindowIndexRepEntry refTablePair = tableIndexesRefCount.get(indexPropKeyMatch);
refTablePair.setRefCount(refTablePair.getRefCount() + 1);
return new Pair<IndexMultiKey, EventTableAndNamePair>(indexPropKeyMatch, new EventTableAndNamePair(refTablePair.getTable(), refTablePair.getOptionalIndexName()));
}
return addIndex(unique, hashProps, btreeProps, prefilledEvents, indexedType, indexName, false);
}
public Pair<IndexMultiKey, EventTableAndNamePair> addTableCreateOrReuse(
List<IndexedPropDesc> hashProps,
List<IndexedPropDesc> btreeProps,
Iterable<EventBean> prefilledEvents,
EventType indexedType,
IndexHint optionalIndexHint,
boolean isIndexShare,
int subqueryNumber,
Set<String> optionalUniqueKeyProps)
{
if (hashProps.isEmpty() && btreeProps.isEmpty()) {
throw new IllegalArgumentException("Invalid zero element list for hash and btree columns");
}
// if there are hints, follow these
if (optionalIndexHint != null) {
Map<IndexMultiKey, NamedWindowIndexRepEntry> indexCandidates = findCandidates(hashProps, btreeProps);
List<IndexHintInstruction> instructions = optionalIndexHint.getInstructionsSubquery(subqueryNumber);
IndexMultiKey found = handleIndexHint(indexCandidates, instructions);
if (found != null) {
return reference(found);
}
}
// Get an existing table, if any, matching the exact requirement, prefer unique
IndexMultiKey indexPropKeyMatch = findExactMatchNameAndType(tableIndexesRefCount.keySet(), true, hashProps, btreeProps);
if (indexPropKeyMatch == null) {
indexPropKeyMatch = findExactMatchNameAndType(tableIndexesRefCount.keySet(), false, hashProps, btreeProps);
}
if (indexPropKeyMatch != null) {
return reference(indexPropKeyMatch);
}
// not found as a full match
// try match on any of the unique indexes
Map<IndexMultiKey, NamedWindowIndexRepEntry> indexCandidates = findCandidates(hashProps, btreeProps);
if (!indexCandidates.isEmpty()) {
for (Map.Entry<IndexMultiKey, NamedWindowIndexRepEntry> indexKey : indexCandidates.entrySet()) {
if (indexKey.getKey().isUnique()) {
return reference(indexKey.getKey());
}
}
}
// not found, see if the named window is declared unique
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 : hashProps) {
if (hashProp.getIndexPropName().equals(uniqueKey)) {
newHashProps.add(hashProp);
found = true;
break;
}
}
if (!found) {
newHashProps = null;
break;
}
}
if (newHashProps != null) {
hashProps = newHashProps;
btreeProps = Collections.emptyList();
unique = true;
coerce = false;
}
}
// not found at all, create
return addIndex(unique, hashProps, btreeProps, prefilledEvents, indexedType, null, coerce);
}
private IndexMultiKey handleIndexHint(Map<IndexMultiKey, NamedWindowIndexRepEntry> 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;
}
private Pair<IndexMultiKey, EventTableAndNamePair> reference(IndexMultiKey found) {
NamedWindowIndexRepEntry refTablePair = tableIndexesRefCount.get(found);
refTablePair.setRefCount(refTablePair.getRefCount() + 1);
return new Pair<IndexMultiKey, EventTableAndNamePair>(found, new EventTableAndNamePair(refTablePair.getTable(), refTablePair.getOptionalIndexName()));
}
private IndexMultiKey findExplicitIndexByName(Map<IndexMultiKey, NamedWindowIndexRepEntry> indexCandidates, String name) {
for (Map.Entry<IndexMultiKey, NamedWindowIndexRepEntry> entry : indexCandidates.entrySet()) {
if (entry.getValue().getOptionalIndexName() != null && entry.getValue().getOptionalIndexName().equals(name)) {
return entry.getKey();
}
}
return null;
}
private IndexMultiKey findExplicitIndexAnyName(Map<IndexMultiKey, NamedWindowIndexRepEntry> indexCandidates) {
for (Map.Entry<IndexMultiKey, NamedWindowIndexRepEntry> entry : indexCandidates.entrySet()) {
if (entry.getValue().getOptionalIndexName() != null) {
return entry.getKey();
}
}
return null;
}
private Map<IndexMultiKey, NamedWindowIndexRepEntry> findCandidates(List<IndexedPropDesc> hashProps, List<IndexedPropDesc> btreeProps) {
Map<IndexMultiKey, NamedWindowIndexRepEntry> indexCandidates = new HashMap<IndexMultiKey, NamedWindowIndexRepEntry>();
for (Map.Entry<IndexMultiKey, NamedWindowIndexRepEntry> entry : tableIndexesRefCount.entrySet()) {
boolean matches = indexMatchesProvided(entry.getKey(), hashProps, btreeProps);
if (matches) {
indexCandidates.put(entry.getKey(), entry.getValue());
}
}
return indexCandidates;
}
private Pair<IndexMultiKey, EventTableAndNamePair> addIndex(boolean unique, List<IndexedPropDesc> hashProps, List<IndexedPropDesc> btreeProps, Iterable<EventBean> prefilledEvents, EventType indexedType, String indexName, boolean mustCoerce) {
// not resolved as full match and not resolved as unique index match, allocate
IndexMultiKey indexPropKey = new IndexMultiKey(unique, hashProps, btreeProps);
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, false);
EventTable table = EventTableUtil.buildIndex(0, indexItem, indexedType, true, unique, indexName);
// fill table since its new
EventBean[] events = new EventBean[1];
for (EventBean prefilledEvent : prefilledEvents)
{
events[0] = prefilledEvent;
table.add(events);
}
// add table
tables.add(table);
// add index, reference counted
tableIndexesRefCount.put(indexPropKey, new NamedWindowIndexRepEntry(table, indexName, 1));
return new Pair<IndexMultiKey, EventTableAndNamePair>(indexPropKey, new EventTableAndNamePair(table, indexName));
}
private 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 boolean indexHashIsProvided(IndexedPropDesc hashPropIndexed, List<IndexedPropDesc> hashPropsProvided) {
for (IndexedPropDesc hashPropProvided : hashPropsProvided) {
if (hashPropProvided.getIndexPropName().equals(hashPropIndexed.getIndexPropName())) {
return true;
}
}
return false;
}
private IndexMultiKey findExactMatchNameAndType(Set<IndexMultiKey> indexMultiKeys, boolean unique, List<IndexedPropDesc> hashProps, List<IndexedPropDesc> btreeProps) {
for (IndexMultiKey existing : indexMultiKeys) {
if (isExactMatch(existing, unique, hashProps, btreeProps)) {
return existing;
}
}
return null;
}
private boolean isExactMatch(IndexMultiKey existing, boolean unique, List<IndexedPropDesc> hashProps, List<IndexedPropDesc> btreeProps) {
if (existing.isUnique() != unique) {
return false;
}
boolean keyPropCompare = IndexedPropDesc.compare(Arrays.asList(existing.getHashIndexedProps()), hashProps);
return keyPropCompare && IndexedPropDesc.compare(Arrays.asList(existing.getRangeIndexedProps()), btreeProps);
}
public void addTableReference(EventTable table) {
for (Map.Entry<IndexMultiKey, NamedWindowIndexRepEntry> entry : tableIndexesRefCount.entrySet())
{
if (entry.getValue().getTable() == table)
{
int current = entry.getValue().getRefCount() + 1;
entry.getValue().setRefCount(current);
}
}
}
/**
* Remove a reference to an index table, decreasing its reference count.
* If the table is no longer used, discard it and no longer update events into the index.
* @param table to remove a reference to
*/
public void removeTableReference(EventTable table)
{
for (Map.Entry<IndexMultiKey, NamedWindowIndexRepEntry> entry : tableIndexesRefCount.entrySet())
{
if (entry.getValue().getTable() == table)
{
int current = entry.getValue().getRefCount();
if (current > 1)
{
current--;
entry.getValue().setRefCount(current);
break;
}
tables.remove(table);
tableIndexesRefCount.remove(entry.getKey());
break;
}
}
}
/**
* Returns a list of current index tables in the repository.
* @return index tables
*/
public List<EventTable> getTables()
{
return tables;
}
/**
* Destroy indexes.
*/
public void destroy()
{
tables.clear();
tableIndexesRefCount.clear();
}
public Pair<IndexMultiKey, EventTableAndNamePair> findTable(Set<String> keyPropertyNames, Set<String> rangePropertyNames, Map<String, EventTable> explicitIndexNames, IndexHint optionalIndexHint) {
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, NamedWindowIndexRepEntry> indexCandidates = findCandidates(hashProps, rangeProps);
// handle hint
if (optionalIndexHint != null) {
List<IndexHintInstruction> instructions = optionalIndexHint.getInstructionsFireAndForget();
IndexMultiKey found = handleIndexHint(indexCandidates, instructions);
if (found != null) {
return getPair(found);
}
}
// no candidates
if (indexCandidates == null || indexCandidates.isEmpty()) {
if (log.isDebugEnabled()) {
log.debug("No index found.");
}
return null;
}
// take the table that has a unique index
for (Map.Entry<IndexMultiKey, NamedWindowIndexRepEntry> entry : indexCandidates.entrySet()) {
if (entry.getKey().isUnique()) {
return getPair(entry.getKey());
}
}
// take the best available table
IndexMultiKey indexMultiKey;
List<IndexMultiKey> indexes = new ArrayList<IndexMultiKey>(indexCandidates.keySet());
if (indexes.size() > 1) {
Comparator<IndexMultiKey> comparator = new Comparator<IndexMultiKey>() {
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;
}
};
Collections.sort(indexes,comparator);
}
indexMultiKey = indexes.get(0);
return getPair(indexMultiKey);
}
private Pair<IndexMultiKey, EventTableAndNamePair> getPair(IndexMultiKey indexMultiKey) {
NamedWindowIndexRepEntry indexFound = tableIndexesRefCount.get(indexMultiKey);
EventTable tableFound = indexFound.getTable();
return new Pair<IndexMultiKey, EventTableAndNamePair>(indexMultiKey, new EventTableAndNamePair(tableFound, indexFound.getOptionalIndexName()));
}
public IndexMultiKey[] getIndexDescriptors() {
Set<IndexMultiKey> keySet = tableIndexesRefCount.keySet();
return keySet.toArray(new IndexMultiKey[keySet.size()]);
}
}