/* *************************************************************************************** * 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.epl.expression.core.ExprIdentNode; import com.espertech.esper.epl.expression.core.ExprNode; import com.espertech.esper.epl.expression.core.ExprNodeUtility; import com.espertech.esper.epl.expression.ops.*; import com.espertech.esper.epl.join.util.Eligibility; import com.espertech.esper.epl.join.util.EligibilityDesc; import com.espertech.esper.epl.join.util.EligibilityUtil; import com.espertech.esper.epl.join.util.RangeFilterAnalyzer; import com.espertech.esper.filter.FilterSpecCompilerMakeParamUtil; import com.espertech.esper.type.RelationalOpEnum; import com.espertech.esper.util.JavaClassHelper; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * Analyzes a filter expression and builds a query graph model. * The 'equals', 'and' 'between' and relational operators expressions in the filter expression are extracted * and placed in the query graph model as navigable relationships (by key and index * properties as well as ranges) between streams. */ public class FilterExprAnalyzer { /** * Analyzes filter expression to build query graph model. * * @param topNode - filter top node * @param queryGraph - model containing relationships between streams, to be written to * @param isOuterJoin indicator for outer join */ public static void analyze(ExprNode topNode, QueryGraph queryGraph, boolean isOuterJoin) { // Analyze relationships between streams. Relationships are properties in AND and EQUALS nodes of joins. if (topNode instanceof ExprEqualsNode) { ExprEqualsNode equalsNode = (ExprEqualsNode) topNode; if (!equalsNode.isNotEquals()) { analyzeEqualsNode(equalsNode, queryGraph, isOuterJoin); } } else if (topNode instanceof ExprAndNode) { ExprAndNode andNode = (ExprAndNode) topNode; analyzeAndNode(andNode, queryGraph, isOuterJoin); } else if (topNode instanceof ExprBetweenNode) { ExprBetweenNode betweenNode = (ExprBetweenNode) topNode; analyzeBetweenNode(betweenNode, queryGraph); } else if (topNode instanceof ExprRelationalOpNode) { ExprRelationalOpNode relNode = (ExprRelationalOpNode) topNode; analyzeRelationalOpNode(relNode, queryGraph); } else if (topNode instanceof FilterExprAnalyzerAffectorProvider) { FilterExprAnalyzerAffectorProvider provider = (FilterExprAnalyzerAffectorProvider) topNode; analyzeAffectorProvider(provider, queryGraph, isOuterJoin); } else if (topNode instanceof ExprInNode) { ExprInNode inNode = (ExprInNode) topNode; analyzeInNode(inNode, queryGraph); } else if (topNode instanceof ExprOrNode) { ExprNode rewritten = FilterSpecCompilerMakeParamUtil.rewriteOrToInIfApplicable(topNode); if (rewritten instanceof ExprInNode) { ExprInNode inNode = (ExprInNode) rewritten; analyzeInNode(inNode, queryGraph); } } } private static void analyzeInNode(ExprInNode inNode, QueryGraph queryGraph) { if (inNode.isNotIn()) { return; } // direction of lookup is value-set (keys) to single-expression (single index) analyzeInNodeSingleIndex(inNode, queryGraph); // direction of lookup is single-expression (key) to value-set (multi index) analyzeInNodeMultiIndex(inNode, queryGraph); } private static void analyzeInNodeMultiIndex(ExprInNode inNode, QueryGraph queryGraph) { ExprNode[] setExpressions = getInNodeSetExpressions(inNode); if (setExpressions.length == 0) { return; } Map<Integer, List<ExprNode>> perStreamExprs = new LinkedHashMap<Integer, List<ExprNode>>(); for (ExprNode exprNodeSet : setExpressions) { if (!(exprNodeSet instanceof ExprIdentNode)) { continue; } ExprIdentNode setIdent = (ExprIdentNode) exprNodeSet; addToList(setIdent.getStreamId(), setIdent, perStreamExprs); } if (perStreamExprs.isEmpty()) { return; } ExprNode testExpr = inNode.getChildNodes()[0]; Class testExprType = JavaClassHelper.getBoxedType(testExpr.getExprEvaluator().getType()); if (perStreamExprs.size() > 1) { return; } Map.Entry<Integer, List<ExprNode>> entry = perStreamExprs.entrySet().iterator().next(); ExprNode[] exprNodes = ExprNodeUtility.toArray(entry.getValue()); for (ExprNode node : exprNodes) { Class exprType = node.getExprEvaluator().getType(); if (JavaClassHelper.getBoxedType(exprType) != testExprType) { return; } } Integer testStreamNum; int setStream = entry.getKey(); if (!(testExpr instanceof ExprIdentNode)) { EligibilityDesc eligibility = EligibilityUtil.verifyInputStream(testExpr, setStream); if (!eligibility.getEligibility().isEligible()) { return; } if (eligibility.getEligibility() == Eligibility.REQUIRE_ONE && setStream == eligibility.getStreamNum()) { return; } testStreamNum = eligibility.getStreamNum(); } else { testStreamNum = ((ExprIdentNode) testExpr).getStreamId(); } if (testStreamNum == null) { queryGraph.addInSetMultiIndexUnkeyed(testExpr, setStream, exprNodes); } else { if (testStreamNum.equals(entry.getKey())) { return; } queryGraph.addInSetMultiIndex(testStreamNum, testExpr, setStream, exprNodes); } } private static void analyzeInNodeSingleIndex(ExprInNode inNode, QueryGraph queryGraph) { if (!(inNode.getChildNodes()[0] instanceof ExprIdentNode)) { return; } ExprIdentNode testIdent = (ExprIdentNode) inNode.getChildNodes()[0]; Class testIdentClass = JavaClassHelper.getBoxedType(testIdent.getExprEvaluator().getType()); int indexedStream = testIdent.getStreamId(); ExprNode[] setExpressions = getInNodeSetExpressions(inNode); if (setExpressions.length == 0) { return; } Map<Integer, List<ExprNode>> perStreamExprs = new LinkedHashMap<Integer, List<ExprNode>>(); for (ExprNode exprNodeSet : setExpressions) { if (JavaClassHelper.getBoxedType(exprNodeSet.getExprEvaluator().getType()) != testIdentClass) { continue; } if (exprNodeSet instanceof ExprIdentNode) { ExprIdentNode setIdent = (ExprIdentNode) exprNodeSet; addToList(setIdent.getStreamId(), setIdent, perStreamExprs); } else { EligibilityDesc eligibility = EligibilityUtil.verifyInputStream(exprNodeSet, indexedStream); if (!eligibility.getEligibility().isEligible()) { continue; } addToList(eligibility.getStreamNum(), exprNodeSet, perStreamExprs); } } for (Map.Entry<Integer, List<ExprNode>> entry : perStreamExprs.entrySet()) { ExprNode[] exprNodes = ExprNodeUtility.toArray(entry.getValue()); if (entry.getKey() == null) { queryGraph.addInSetSingleIndexUnkeyed(testIdent.getStreamId(), testIdent, exprNodes); continue; } if (entry.getKey() != indexedStream) { queryGraph.addInSetSingleIndex(testIdent.getStreamId(), testIdent, entry.getKey(), exprNodes); } } } private static void addToList(Integer streamIdAllowNull, ExprNode expr, Map<Integer, List<ExprNode>> perStreamExpression) { List<ExprNode> perStream = perStreamExpression.get(streamIdAllowNull); if (perStream == null) { perStream = new ArrayList<ExprNode>(); perStreamExpression.put(streamIdAllowNull, perStream); } perStream.add(expr); } private static ExprNode[] getInNodeSetExpressions(ExprInNode inNode) { ExprNode[] setExpressions = new ExprNode[inNode.getChildNodes().length - 1]; int count = 0; for (int i = 1; i < inNode.getChildNodes().length; i++) { setExpressions[count++] = inNode.getChildNodes()[i]; } return setExpressions; } private static void analyzeAffectorProvider(FilterExprAnalyzerAffectorProvider provider, QueryGraph queryGraph, boolean isOuterJoin) { FilterExprAnalyzerAffector affector = provider.getAffector(isOuterJoin); if (affector == null) { return; } affector.apply(queryGraph); } private static void analyzeRelationalOpNode(ExprRelationalOpNode relNode, QueryGraph queryGraph) { if (((relNode.getChildNodes()[0] instanceof ExprIdentNode)) && ((relNode.getChildNodes()[1] instanceof ExprIdentNode))) { ExprIdentNode identNodeLeft = (ExprIdentNode) relNode.getChildNodes()[0]; ExprIdentNode identNodeRight = (ExprIdentNode) relNode.getChildNodes()[1]; if (identNodeLeft.getStreamId() != identNodeRight.getStreamId()) { queryGraph.addRelationalOpStrict(identNodeLeft.getStreamId(), identNodeLeft, identNodeRight.getStreamId(), identNodeRight, relNode.getRelationalOpEnum()); } return; } int indexedStream = -1; ExprIdentNode indexedPropExpr = null; ExprNode exprNodeNoIdent = null; RelationalOpEnum relop = relNode.getRelationalOpEnum(); if (relNode.getChildNodes()[0] instanceof ExprIdentNode) { indexedPropExpr = (ExprIdentNode) relNode.getChildNodes()[0]; indexedStream = indexedPropExpr.getStreamId(); exprNodeNoIdent = relNode.getChildNodes()[1]; } else if (relNode.getChildNodes()[1] instanceof ExprIdentNode) { indexedPropExpr = (ExprIdentNode) relNode.getChildNodes()[1]; indexedStream = indexedPropExpr.getStreamId(); exprNodeNoIdent = relNode.getChildNodes()[0]; relop = relop.reversed(); } if (indexedStream == -1) { return; // require property of right/left side of equals } EligibilityDesc eligibility = EligibilityUtil.verifyInputStream(exprNodeNoIdent, indexedStream); if (!eligibility.getEligibility().isEligible()) { return; } queryGraph.addRelationalOp(indexedStream, indexedPropExpr, eligibility.getStreamNum(), exprNodeNoIdent, relop); } private static void analyzeBetweenNode(ExprBetweenNode betweenNode, QueryGraph queryGraph) { RangeFilterAnalyzer.apply(betweenNode.getChildNodes()[0], betweenNode.getChildNodes()[1], betweenNode.getChildNodes()[2], betweenNode.isLowEndpointIncluded(), betweenNode.isHighEndpointIncluded(), betweenNode.isNotBetween(), queryGraph); } /** * Analye EQUALS (=) node. * * @param equalsNode - node to analyze * @param queryGraph - store relationships between stream properties * @param isOuterJoin indicator for outer join */ protected static void analyzeEqualsNode(ExprEqualsNode equalsNode, QueryGraph queryGraph, boolean isOuterJoin) { if ((equalsNode.getChildNodes()[0] instanceof ExprIdentNode) && (equalsNode.getChildNodes()[1] instanceof ExprIdentNode)) { ExprIdentNode identNodeLeft = (ExprIdentNode) equalsNode.getChildNodes()[0]; ExprIdentNode identNodeRight = (ExprIdentNode) equalsNode.getChildNodes()[1]; if (identNodeLeft.getStreamId() != identNodeRight.getStreamId()) { queryGraph.addStrictEquals(identNodeLeft.getStreamId(), identNodeLeft.getResolvedPropertyName(), identNodeLeft, identNodeRight.getStreamId(), identNodeRight.getResolvedPropertyName(), identNodeRight); } return; } if (isOuterJoin) { // outerjoins don't use constants or one-way expression-derived information to evaluate join return; } // handle constant-compare or transformation case int indexedStream = -1; ExprIdentNode indexedPropExpr = null; ExprNode exprNodeNoIdent = null; if (equalsNode.getChildNodes()[0] instanceof ExprIdentNode) { indexedPropExpr = (ExprIdentNode) equalsNode.getChildNodes()[0]; indexedStream = indexedPropExpr.getStreamId(); exprNodeNoIdent = equalsNode.getChildNodes()[1]; } else if (equalsNode.getChildNodes()[1] instanceof ExprIdentNode) { indexedPropExpr = (ExprIdentNode) equalsNode.getChildNodes()[1]; indexedStream = indexedPropExpr.getStreamId(); exprNodeNoIdent = equalsNode.getChildNodes()[0]; } if (indexedStream == -1) { return; // require property of right/left side of equals } EligibilityDesc eligibility = EligibilityUtil.verifyInputStream(exprNodeNoIdent, indexedStream); if (!eligibility.getEligibility().isEligible()) { return; } if (eligibility.getEligibility() == Eligibility.REQUIRE_NONE) { queryGraph.addUnkeyedExpression(indexedStream, indexedPropExpr, exprNodeNoIdent); } else { queryGraph.addKeyedExpression(indexedStream, indexedPropExpr, eligibility.getStreamNum(), exprNodeNoIdent); } } /** * Analyze the AND-node. * * @param andNode - node to analyze * @param queryGraph - to store relationships between stream properties * @param isOuterJoin indicator for outer join */ protected static void analyzeAndNode(ExprAndNode andNode, QueryGraph queryGraph, boolean isOuterJoin) { for (ExprNode childNode : andNode.getChildNodes()) { analyze(childNode, queryGraph, isOuterJoin); } } }