/*
***************************************************************************************
* 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.join.plan;
import com.espertech.esper.client.EventType;
import com.espertech.esper.collection.Pair;
import com.espertech.esper.epl.expression.core.ExprIdentNode;
import com.espertech.esper.epl.expression.core.ExprIdentNodeImpl;
import com.espertech.esper.epl.expression.core.ExprNode;
import com.espertech.esper.epl.join.hint.ExcludePlanFilterOperatorType;
import com.espertech.esper.epl.join.hint.ExcludePlanHint;
import com.espertech.esper.type.RelationalOpEnum;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;
/**
* Model of relationships between streams based on properties in both streams that are
* specified as equal in a filter expression.
*/
public class QueryGraph {
public final static int SELF_STREAM = Integer.MIN_VALUE;
private final int numStreams;
private final ExcludePlanHint optionalHint;
private final boolean nToZeroAnalysis; // for subqueries and on-action
private final Map<QueryGraphKey, QueryGraphValue> streamJoinMap;
/**
* Ctor.
*
* @param numStreams - number of streams
* @param optionalHint hint if any
* @param nToZeroAnalysis indicator for star-eval
*/
public QueryGraph(int numStreams, ExcludePlanHint optionalHint, boolean nToZeroAnalysis) {
this.numStreams = numStreams;
this.optionalHint = optionalHint;
this.nToZeroAnalysis = nToZeroAnalysis;
streamJoinMap = new HashMap<QueryGraphKey, QueryGraphValue>();
}
/**
* Returns the number of streams.
*
* @return number of streams
*/
public int getNumStreams() {
return numStreams;
}
/**
* Add properties for 2 streams that are equal.
*
* @param streamLeft - left hand stream
* @param propertyLeft - left hand stream property
* @param streamRight - right hand stream
* @param propertyRight - right hand stream property
* @param nodeLeft left expr
* @param nodeRight right expr
* @return true if added and did not exist, false if already known
*/
public boolean addStrictEquals(int streamLeft, String propertyLeft, ExprIdentNode nodeLeft, int streamRight, String propertyRight, ExprIdentNode nodeRight) {
check(streamLeft, streamRight);
if (propertyLeft == null || propertyRight == null) {
throw new IllegalArgumentException("Null property names supplied");
}
if (streamLeft == streamRight) {
throw new IllegalArgumentException("Streams supplied are the same");
}
boolean addedLeft = internalAddEquals(streamLeft, propertyLeft, nodeLeft, streamRight, nodeRight);
boolean addedRight = internalAddEquals(streamRight, propertyRight, nodeRight, streamLeft, nodeLeft);
return addedLeft || addedRight;
}
public boolean isNavigableAtAll(int streamFrom, int streamTo) {
QueryGraphKey key = new QueryGraphKey(streamFrom, streamTo);
QueryGraphValue value = streamJoinMap.get(key);
return value != null && !value.isEmptyNotNavigable();
}
/**
* Returns set of streams that the given stream is navigable to.
*
* @param streamFrom - from stream number
* @return set of streams related to this stream, or empty set if none
*/
public Set<Integer> getNavigableStreams(int streamFrom) {
Set<Integer> result = new HashSet<Integer>();
for (int i = 0; i < numStreams; i++) {
if (isNavigableAtAll(streamFrom, i)) {
result.add(i);
}
}
return result;
}
public QueryGraphValue getGraphValue(int streamLookup, int streamIndexed) {
QueryGraphKey key = new QueryGraphKey(streamLookup, streamIndexed);
QueryGraphValue value = streamJoinMap.get(key);
if (value != null) {
return value;
}
return new QueryGraphValue();
}
/**
* Fill in equivalent key properties (navigation entries) on all streams.
* For example, if a=b and b=c then addRelOpInternal a=c. The method adds new equalivalent key properties
* until no additional entries to be added are found, ie. several passes can be made.
*
* @param queryGraph - navigablity info between streamss
* @param typesPerStream type info
*/
public static void fillEquivalentNav(EventType[] typesPerStream, QueryGraph queryGraph) {
boolean addedEquivalency;
// Repeat until no more entries were added
do {
addedEquivalency = false;
// For each stream-to-stream combination
for (int lookupStream = 0; lookupStream < queryGraph.numStreams; lookupStream++) {
for (int indexedStream = 0; indexedStream < queryGraph.numStreams; indexedStream++) {
if (lookupStream == indexedStream) {
continue;
}
boolean added = fillEquivalentNav(typesPerStream, queryGraph, lookupStream, indexedStream);
if (added) {
addedEquivalency = true;
}
}
}
}
while (addedEquivalency);
}
/*
* Looks at the key and index (aka. left and right) properties of the 2 streams and checks
* for each property if any equivalent index properties exist for other streams.
*/
private static boolean fillEquivalentNav(EventType[] typesPerStream, QueryGraph queryGraph, int lookupStream, int indexedStream) {
boolean addedEquivalency = false;
QueryGraphValue value = queryGraph.getGraphValue(lookupStream, indexedStream);
if (value.isEmptyNotNavigable()) {
return false;
}
QueryGraphValuePairHashKeyIndex hashKeys = value.getHashKeyProps();
String[] strictKeyProps = hashKeys.getStrictKeys();
String[] indexProps = hashKeys.getIndexed();
if (strictKeyProps.length == 0) {
return false;
}
if (strictKeyProps.length != indexProps.length) {
throw new IllegalStateException("Unexpected key and index property number mismatch");
}
for (int i = 0; i < strictKeyProps.length; i++) {
if (strictKeyProps[i] == null) {
continue; // not a strict key
}
boolean added = fillEquivalentNav(typesPerStream, queryGraph, lookupStream, strictKeyProps[i], indexedStream, indexProps[i]);
if (added) {
addedEquivalency = true;
}
}
return addedEquivalency;
}
/*
* Looks at the key and index (aka. left and right) properties of the 2 streams and checks
* for each property if any equivalent index properties exist for other streams.
*
* Example: s0.p0 = s1.p1 and s1.p1 = s2.p2 ==> therefore s0.p0 = s2.p2
* ==> look stream s0, property p0; indexed stream s1, property p1
* Is there any other lookup stream that has stream 1 and property p1 as index property? ==> this is stream s2, p2
* Add navigation entry between stream s0 and property p0 to stream s2, property p2
*/
private static boolean fillEquivalentNav(EventType[] typesPerStream, QueryGraph queryGraph, int lookupStream, String keyProp, int indexedStream, String indexProp) {
boolean addedEquivalency = false;
for (int otherStream = 0; otherStream < queryGraph.numStreams; otherStream++) {
if ((otherStream == lookupStream) || (otherStream == indexedStream)) {
continue;
}
QueryGraphValue value = queryGraph.getGraphValue(otherStream, indexedStream);
QueryGraphValuePairHashKeyIndex hashKeys = value.getHashKeyProps();
String[] otherStrictKeyProps = hashKeys.getStrictKeys();
String[] otherIndexProps = hashKeys.getIndexed();
int otherPropertyNum = -1;
if (otherIndexProps == null) {
continue;
}
for (int i = 0; i < otherIndexProps.length; i++) {
if (otherIndexProps[i].equals(indexProp)) {
otherPropertyNum = i;
break;
}
}
if (otherPropertyNum != -1) {
if (otherStrictKeyProps[otherPropertyNum] != null) {
ExprIdentNode identNodeLookup = new ExprIdentNodeImpl(typesPerStream[lookupStream], keyProp, lookupStream);
ExprIdentNode identNodeOther = new ExprIdentNodeImpl(typesPerStream[otherStream], otherStrictKeyProps[otherPropertyNum], otherStream);
boolean added = queryGraph.addStrictEquals(lookupStream, keyProp, identNodeLookup, otherStream, otherStrictKeyProps[otherPropertyNum], identNodeOther);
if (added) {
addedEquivalency = true;
}
}
}
}
return addedEquivalency;
}
public String toString() {
StringWriter buf = new StringWriter();
PrintWriter writer = new PrintWriter(buf);
int count = 0;
for (Map.Entry<QueryGraphKey, QueryGraphValue> entry : streamJoinMap.entrySet()) {
count++;
writer.println("Entry " + count + ": key=" + entry.getKey());
writer.println(" value=" + entry.getValue());
}
return buf.toString();
}
public void addRangeStrict(int streamNumStart, ExprIdentNode propertyStartExpr,
int streamNumEnd, ExprIdentNode propertyEndExpr,
int streamNumValue, ExprIdentNode propertyValueExpr,
QueryGraphRangeEnum rangeOp) {
check(streamNumStart, streamNumValue);
check(streamNumEnd, streamNumValue);
// add as a range if the endpoints are from the same stream
if (streamNumStart == streamNumEnd && streamNumStart != streamNumValue) {
internalAddRange(streamNumStart, streamNumValue, rangeOp, propertyStartExpr, propertyEndExpr, propertyValueExpr);
internalAddRelOp(streamNumValue, streamNumStart, propertyValueExpr, QueryGraphRangeEnum.GREATER_OR_EQUAL, propertyEndExpr, false);
internalAddRelOp(streamNumValue, streamNumStart, propertyValueExpr, QueryGraphRangeEnum.LESS_OR_EQUAL, propertyStartExpr, false);
} else {
// endpoints from a different stream, add individually
if (streamNumValue != streamNumStart) {
// read propertyValue >= propertyStart
internalAddRelOp(streamNumStart, streamNumValue, propertyStartExpr, QueryGraphRangeEnum.GREATER_OR_EQUAL, propertyValueExpr, true);
// read propertyStart <= propertyValue
internalAddRelOp(streamNumValue, streamNumStart, propertyValueExpr, QueryGraphRangeEnum.LESS_OR_EQUAL, propertyStartExpr, true);
}
if (streamNumValue != streamNumEnd) {
// read propertyValue <= propertyEnd
internalAddRelOp(streamNumEnd, streamNumValue, propertyEndExpr, QueryGraphRangeEnum.LESS_OR_EQUAL, propertyValueExpr, true);
// read propertyEnd >= propertyValue
internalAddRelOp(streamNumValue, streamNumEnd, propertyValueExpr, QueryGraphRangeEnum.GREATER_OR_EQUAL, propertyEndExpr, true);
}
}
}
public void addRelationalOpStrict(int streamIdLeft, ExprIdentNode propertyLeftExpr,
int streamIdRight, ExprIdentNode propertyRightExpr,
RelationalOpEnum relationalOpEnum) {
check(streamIdLeft, streamIdRight);
internalAddRelOp(streamIdLeft, streamIdRight, propertyLeftExpr, QueryGraphRangeEnum.mapFrom(relationalOpEnum.reversed()), propertyRightExpr, false);
internalAddRelOp(streamIdRight, streamIdLeft, propertyRightExpr, QueryGraphRangeEnum.mapFrom(relationalOpEnum), propertyLeftExpr, false);
}
public void addUnkeyedExpression(int indexedStream, ExprIdentNode indexedProp, ExprNode exprNodeNoIdent) {
if (indexedStream < 0 || indexedStream >= numStreams) {
throw new IllegalArgumentException("Invalid indexed stream " + indexedStream);
}
if (numStreams > 1) {
for (int i = 0; i < numStreams; i++) {
if (i != indexedStream) {
internalAddEqualsUnkeyed(i, indexedStream, indexedProp, exprNodeNoIdent);
}
}
} else {
internalAddEqualsUnkeyed(SELF_STREAM, indexedStream, indexedProp, exprNodeNoIdent);
}
}
public void addKeyedExpression(int indexedStream, ExprIdentNode indexedProp, int keyExprStream, ExprNode exprNodeNoIdent) {
check(indexedStream, keyExprStream);
internalAddEqualsNoProp(keyExprStream, indexedStream, indexedProp, exprNodeNoIdent);
}
private void check(int indexedStream, int keyStream) {
if (indexedStream < 0 || indexedStream >= numStreams) {
throw new IllegalArgumentException("Invalid indexed stream " + indexedStream);
}
if (keyStream >= numStreams) {
throw new IllegalArgumentException("Invalid key stream " + keyStream);
}
if (numStreams > 1) {
if (keyStream < 0) {
throw new IllegalArgumentException("Invalid key stream " + keyStream);
}
} else {
if (keyStream != SELF_STREAM) {
throw new IllegalArgumentException("Invalid key stream " + keyStream);
}
}
if (keyStream == indexedStream) {
throw new IllegalArgumentException("Invalid key stream equals indexed stream " + keyStream);
}
}
public void addRangeExpr(int indexedStream, ExprIdentNode indexedProp, ExprNode startNode, Integer optionalStartStreamNum, ExprNode endNode, Integer optionalEndStreamNum, QueryGraphRangeEnum rangeOp) {
if (optionalStartStreamNum == null && optionalEndStreamNum == null) {
if (numStreams > 1) {
for (int i = 0; i < numStreams; i++) {
if (i == indexedStream) {
continue;
}
internalAddRange(i, indexedStream, rangeOp, startNode, endNode, indexedProp);
}
} else {
internalAddRange(SELF_STREAM, indexedStream, rangeOp, startNode, endNode, indexedProp);
}
return;
}
optionalStartStreamNum = optionalStartStreamNum != null ? optionalStartStreamNum : -1;
optionalEndStreamNum = optionalEndStreamNum != null ? optionalEndStreamNum : -1;
// add for a specific stream only
if (optionalStartStreamNum.equals(optionalEndStreamNum) || optionalEndStreamNum.equals(-1)) {
internalAddRange(optionalStartStreamNum, indexedStream, rangeOp, startNode, endNode, indexedProp);
}
if (optionalStartStreamNum.equals(-1)) {
internalAddRange(optionalEndStreamNum, indexedStream, rangeOp, startNode, endNode, indexedProp);
}
}
public void addRelationalOp(int indexedStream, ExprIdentNode indexedProp, Integer keyStreamNum, ExprNode exprNodeNoIdent, RelationalOpEnum relationalOpEnum) {
if (keyStreamNum == null) {
if (numStreams > 1) {
for (int i = 0; i < numStreams; i++) {
if (i == indexedStream) {
continue;
}
internalAddRelOp(i, indexedStream, exprNodeNoIdent, QueryGraphRangeEnum.mapFrom(relationalOpEnum), indexedProp, false);
}
} else {
internalAddRelOp(SELF_STREAM, indexedStream, exprNodeNoIdent, QueryGraphRangeEnum.mapFrom(relationalOpEnum), indexedProp, false);
}
return;
}
// add for a specific stream only
internalAddRelOp(keyStreamNum, indexedStream, exprNodeNoIdent, QueryGraphRangeEnum.mapFrom(relationalOpEnum), indexedProp, false);
}
public void addInSetSingleIndex(int testStreamNum, ExprNode testPropExpr, int setStreamNum, ExprNode[] setPropExpr) {
check(testStreamNum, setStreamNum);
internalAddInKeywordSingleIndex(setStreamNum, testStreamNum, testPropExpr, setPropExpr);
}
public void addInSetSingleIndexUnkeyed(int testStreamNum, ExprNode testPropExpr, ExprNode[] setPropExpr) {
if (numStreams > 1) {
for (int i = 0; i < numStreams; i++) {
if (i != testStreamNum) {
internalAddInKeywordSingleIndex(i, testStreamNum, testPropExpr, setPropExpr);
}
}
} else {
internalAddInKeywordSingleIndex(SELF_STREAM, testStreamNum, testPropExpr, setPropExpr);
}
}
public void addInSetMultiIndex(int testStreamNum, ExprNode testPropExpr, int setStreamNum, ExprNode[] setPropExpr) {
check(testStreamNum, setStreamNum);
internalAddInKeywordMultiIndex(testStreamNum, setStreamNum, testPropExpr, setPropExpr);
}
public void addInSetMultiIndexUnkeyed(ExprNode testPropExpr, int setStreamNum, ExprNode[] setPropExpr) {
for (int i = 0; i < numStreams; i++) {
if (i != setStreamNum) {
internalAddInKeywordMultiIndex(i, setStreamNum, testPropExpr, setPropExpr);
}
}
}
public void addCustomIndex(String operationName, ExprNode[] indexExpressions, List<Pair<ExprNode, int[]>> streamKeys, int streamValue) {
int expressionPosition = 0;
for (Pair<ExprNode, int[]> pair : streamKeys) {
if (pair.getSecond().length == 0) {
if (numStreams > 1) {
for (int i = 0; i < numStreams; i++) {
QueryGraphValue value = getCreateValue(i, streamValue);
value.addCustom(indexExpressions, operationName, expressionPosition, pair.getFirst());
}
} else {
QueryGraphValue value = getCreateValue(SELF_STREAM, streamValue);
value.addCustom(indexExpressions, operationName, expressionPosition, pair.getFirst());
}
} else {
for (int providingStream : pair.getSecond()) {
QueryGraphValue value = getCreateValue(providingStream, streamValue);
value.addCustom(indexExpressions, operationName, expressionPosition, pair.getFirst());
}
}
expressionPosition++;
}
}
private void internalAddRange(int streamKey, int streamValue, QueryGraphRangeEnum rangeOp, ExprNode propertyStartExpr, ExprNode propertyEndExpr, ExprIdentNode propertyValueExpr) {
if (nToZeroAnalysis && streamValue != 0) {
return;
}
if (optionalHint != null && optionalHint.filter(streamKey, streamValue, ExcludePlanFilterOperatorType.RELOP)) {
return;
}
QueryGraphValue valueLeft = getCreateValue(streamKey, streamValue);
valueLeft.addRange(rangeOp, propertyStartExpr, propertyEndExpr, propertyValueExpr);
}
private void internalAddRelOp(int streamKey, int streamValue, ExprNode keyExpr, QueryGraphRangeEnum rangeEnum, ExprIdentNode valueExpr, boolean isBetweenOrIn) {
if (nToZeroAnalysis && streamValue != 0) {
return;
}
if (optionalHint != null && optionalHint.filter(streamKey, streamValue, ExcludePlanFilterOperatorType.RELOP)) {
return;
}
QueryGraphValue value = getCreateValue(streamKey, streamValue);
value.addRelOp(keyExpr, rangeEnum, valueExpr, isBetweenOrIn);
}
private boolean internalAddEquals(int streamLookup, String propertyLookup, ExprIdentNode propertyLookupNode, int streamIndexed, ExprIdentNode propertyIndexedNode) {
if (nToZeroAnalysis && streamIndexed != 0) {
return false;
}
if (optionalHint != null && optionalHint.filter(streamLookup, propertyIndexedNode.getStreamId(), ExcludePlanFilterOperatorType.EQUALS, propertyLookupNode, propertyIndexedNode)) {
return false;
}
QueryGraphValue value = getCreateValue(streamLookup, streamIndexed);
return value.addStrictCompare(propertyLookup, propertyLookupNode, propertyIndexedNode);
}
private void internalAddEqualsNoProp(int keyExprStream, int indexedStream, ExprIdentNode indexedProp, ExprNode exprNodeNoIdent) {
if (nToZeroAnalysis && indexedStream != 0) {
return;
}
if (optionalHint != null && optionalHint.filter(keyExprStream, indexedStream, ExcludePlanFilterOperatorType.EQUALS)) {
return;
}
QueryGraphValue value = getCreateValue(keyExprStream, indexedStream);
value.addKeyedExpr(indexedProp, exprNodeNoIdent);
}
private void internalAddEqualsUnkeyed(int streamKey, int streamValue, ExprIdentNode indexedProp, ExprNode exprNodeNoIdent) {
if (nToZeroAnalysis && streamValue != 0) {
return;
}
if (optionalHint != null && optionalHint.filter(streamKey, streamValue, ExcludePlanFilterOperatorType.EQUALS)) {
return;
}
QueryGraphValue value = getCreateValue(streamKey, streamValue);
value.addUnkeyedExpr(indexedProp, exprNodeNoIdent);
}
private void internalAddInKeywordSingleIndex(int streamKey, int streamValue, ExprNode testPropExpr, ExprNode[] setPropExpr) {
if (nToZeroAnalysis && streamValue != 0) {
return;
}
if (optionalHint != null && optionalHint.filter(streamKey, streamValue, ExcludePlanFilterOperatorType.INKW)) {
return;
}
QueryGraphValue valueSingleIdx = getCreateValue(streamKey, streamValue);
valueSingleIdx.addInKeywordSingleIdx(testPropExpr, setPropExpr);
}
private void internalAddInKeywordMultiIndex(int streamKey, int streamValue, ExprNode testPropExpr, ExprNode[] setPropExpr) {
if (nToZeroAnalysis && streamValue != 0) {
return;
}
if (optionalHint != null && optionalHint.filter(streamKey, streamValue, ExcludePlanFilterOperatorType.INKW)) {
return;
}
QueryGraphValue value = getCreateValue(streamKey, streamValue);
value.addInKeywordMultiIdx(testPropExpr, setPropExpr);
}
private QueryGraphValue getCreateValue(int streamKey, int streamValue) {
check(streamValue, streamKey);
QueryGraphKey key = new QueryGraphKey(streamKey, streamValue);
QueryGraphValue value = streamJoinMap.get(key);
if (value == null) {
value = new QueryGraphValue();
streamJoinMap.put(key, value);
}
return value;
}
}