/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to you under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.eigenbase.sarg; import java.util.*; import org.eigenbase.relopt.*; import org.eigenbase.rex.*; import org.eigenbase.sql.*; import org.eigenbase.sql.fun.*; /** * SargRexAnalyzer attempts to translate a rex predicate into a {@link * SargBinding}. It assumes that the predicate expression is already * well-formed. */ public class SargRexAnalyzer { //~ Instance fields -------------------------------------------------------- private final SargFactory factory; /** * If true, conjuntions on the same input reference are disallowed, as well * as all disjunctions. Also, only a single range predicate is allowed. */ private final boolean simpleMode; private final Map<SqlOperator, CallConvertlet> convertletMap; private boolean failed; private RexInputRef boundInputRef; private RexNode coordinate; private boolean variableSeen; private boolean reverse; private List<SargExpr> exprStack; private List<RexNode> nonSargFilterList; private List<SargBinding> sargBindingList; private Map<SargExpr, RexNode> sarg2RexMap; /** * If >= 0, treat RexInputRefs whose index is within the range * [lowerRexInputIdx, upperRexInputIdx) as coordinates in expressions */ private int lowerRexInputIdx; /** * If >= 0, treat RexInputRefs whose index is within the range * [lowerRexInputIdx, upperRexInputIdx) as coordinates in expressions */ private int upperRexInputIdx; //~ Constructors ----------------------------------------------------------- SargRexAnalyzer( SargFactory factory, boolean simpleMode) { this(factory, simpleMode, -1, -1); } SargRexAnalyzer( SargFactory factory, boolean simpleMode, int lowerRexInputRef, int upperRexInputRef) { this.factory = factory; this.simpleMode = simpleMode; this.lowerRexInputIdx = lowerRexInputRef; this.upperRexInputIdx = upperRexInputRef; assert (lowerRexInputIdx < 0 && upperRexInputIdx < 0) || (lowerRexInputIdx >= 0 && upperRexInputIdx >= 0); convertletMap = new HashMap<SqlOperator, CallConvertlet>(); registerConvertlet( SqlStdOperatorTable.EQUALS, new ComparisonConvertlet( null, SargStrictness.CLOSED)); registerConvertlet( SqlStdOperatorTable.IS_NULL, new ComparisonConvertlet( null, SargStrictness.CLOSED)); registerConvertlet( SqlStdOperatorTable.IS_TRUE, new ComparisonConvertlet( null, SargStrictness.CLOSED)); registerConvertlet( SqlStdOperatorTable.IS_FALSE, new ComparisonConvertlet( null, SargStrictness.CLOSED)); registerConvertlet( SqlStdOperatorTable.IS_UNKNOWN, new ComparisonConvertlet( null, SargStrictness.CLOSED)); registerConvertlet( SqlStdOperatorTable.LESS_THAN, new ComparisonConvertlet( SargBoundType.UPPER, SargStrictness.OPEN)); registerConvertlet( SqlStdOperatorTable.LESS_THAN_OR_EQUAL, new ComparisonConvertlet( SargBoundType.UPPER, SargStrictness.CLOSED)); registerConvertlet( SqlStdOperatorTable.GREATER_THAN, new ComparisonConvertlet( SargBoundType.LOWER, SargStrictness.OPEN)); registerConvertlet( SqlStdOperatorTable.GREATER_THAN_OR_EQUAL, new ComparisonConvertlet( SargBoundType.LOWER, SargStrictness.CLOSED)); registerConvertlet( SqlStdOperatorTable.AND, new BooleanConvertlet( SargSetOperator.INTERSECTION)); if (!simpleMode) { registerConvertlet( SqlStdOperatorTable.OR, new BooleanConvertlet( SargSetOperator.UNION)); } registerConvertlet( SqlStdOperatorTable.NOT, new BooleanConvertlet( SargSetOperator.COMPLEMENT)); // TODO: likeOperator (via complement notLikeOperator) // TODO: non-literal constants (e.g. CURRENT_USER) } //~ Methods ---------------------------------------------------------------- private void registerConvertlet( SqlOperator op, CallConvertlet convertlet) { convertletMap.put(op, convertlet); } // REVIEW jvs 18-Mar-2006: rename these two to decomposeConjunction // and recomposeConjunction so "one not skilled in the art" will // still have a chance of figuring out what they're for just // from the names. Also, some explanation of the state maintained // by SargRexAnalyzer across the various calls is in order. // (It used to be one-shot, but no longer.) /** * Reconstructs a rex predicate from a list of SargExprs which will be * AND'ed together. */ private void recomposeConjunction() { for (int i = 0; i < sargBindingList.size(); i++) { final SargBinding currBinding = sargBindingList.get(i); final RexInputRef currRef = currBinding.getInputRef(); SargExpr currSargExpr = currBinding.getExpr(); RexNode currAndNode = sarg2RexMap.get(currSargExpr); // don't need this anymore // will be have new mapping put back if currSargExpr remain // unchanged. sarg2RexMap.remove(currSargExpr); boolean recomp = false; // search the rest of the list to find SargExpr on the same col. ListIterator<SargBinding> iter = sargBindingList.listIterator(i + 1); while (iter.hasNext()) { final SargBinding nextBinding = iter.next(); final RexInputRef nextRef = nextBinding.getInputRef(); final SargExpr nextSargExpr = nextBinding.getExpr(); if (nextRef.getIndex() == currRef.getIndex()) { // build new SargExpr SargSetExpr expr = factory.newSetExpr( currSargExpr.getDataType(), SargSetOperator.INTERSECTION); expr.addChild(currSargExpr); expr.addChild(nextSargExpr); // build new RexNode currAndNode = factory.getRexBuilder().makeCall( SqlStdOperatorTable.AND, currAndNode, sarg2RexMap.get(nextSargExpr)); currSargExpr = expr; sarg2RexMap.remove(nextSargExpr); iter.remove(); recomp = true; } } if (recomp) { assert !simpleMode; if (!testDynamicParamSupport(currSargExpr)) { // Oops, we can't actually support the conjunction we // recomposed. Toss it. (We could do a better job by at // least using part of it, but the effort might be better // spent on implementing deferred expression evaluation.) nonSargFilterList.add(currAndNode); sargBindingList.remove(i); continue; } } if (recomp) { SargBinding newBinding = new SargBinding(currSargExpr, currRef); sargBindingList.remove(i); sargBindingList.add(i, newBinding); } sarg2RexMap.put(currSargExpr, currAndNode); } } /** * Analyzes a rex predicate. * * @param rexPredicate predicate to be analyzed * @return a list of SargBindings contained in the input rex predicate */ public List<SargBinding> analyzeAll(RexNode rexPredicate) { sargBindingList = new ArrayList<SargBinding>(); sarg2RexMap = new HashMap<SargExpr, RexNode>(); nonSargFilterList = new ArrayList<RexNode>(); // Flatten out the RexNode tree into a list of terms that // are AND'ed together final List<RexNode> rexCFList = RelOptUtil.conjunctions(rexPredicate); // In simple mode, each input ref can only be referenced once, so // keep a list of them. We also only allow one non-point expression. List<Integer> boundRefList = new ArrayList<Integer>(); boolean rangeFound = false; for (RexNode rexPred : rexCFList) { final SargBinding sargBinding = analyze(rexPred); if (sargBinding != null) { if (simpleMode) { RexInputRef inputRef = sargBinding.getInputRef(); if (boundRefList.contains(inputRef.getIndex())) { nonSargFilterList.add(rexPred); continue; } else { boundRefList.add(inputRef.getIndex()); } SargIntervalSequence sargSeq = sargBinding.getExpr().evaluate(); if (sargSeq.isRange()) { if (rangeFound) { nonSargFilterList.add(rexPred); continue; } else { rangeFound = true; } } } sargBindingList.add(sargBinding); sarg2RexMap.put( sargBinding.getExpr(), rexPred); } else { nonSargFilterList.add(rexPred); } } // Reset the state variables used during analyze, just for sanity sake. failed = false; boundInputRef = null; clearLeaf(); // Combine the AND terms back together. recomposeConjunction(); return sargBindingList; } /** * Tests whether we can support the usage of dynamic parameters in a given * SargExpr. * * @param sargExpr expression to test * @return true if supported */ private boolean testDynamicParamSupport(SargExpr sargExpr) { // NOTE jvs 18-Mar-2006: we don't currently support boolean operators // over predicates on dynamic parameters. The reason is that we // evaluate sarg expressions at prepare time rather than at execution // time. To support them, we would need a way to defer evaluation // until execution time (Broadbase used to do that). Set<RexDynamicParam> dynamicParams = new HashSet<RexDynamicParam>(); sargExpr.collectDynamicParams(dynamicParams); if (dynamicParams.isEmpty()) { // no dynamic params, no problem return true; } if (sargExpr instanceof SargIntervalExpr) { // We can support a single point or ray, which is the // most that would have been generated by the time this is // called. Should probably assert that. return true; } // Anything else is unsupported. return false; } /** * Reconstructs a rex predicate from the non-sargable filter predicates * which are AND'ed together. * * @return the rex predicate reconstructed from the non-sargable predicates. */ public RexNode getNonSargFilterRexNode() { if (nonSargFilterList.isEmpty()) { return null; } RexNode newAndNode = nonSargFilterList.get(0); for (int i = 1; i < nonSargFilterList.size(); i++) { newAndNode = factory.getRexBuilder().makeCall( SqlStdOperatorTable.AND, newAndNode, nonSargFilterList.get(i)); } return newAndNode; } /** * @deprecated use {@link #getNonSargFilterRexNode()} */ public RexNode getPostFilterRexNode() { return getNonSargFilterRexNode(); } /** * Reconstructs a rex predicate from a list of SargBindings which are AND'ed * together. * * @param sargBindingList list of SargBindings to be converted. * @return the rex predicate reconstructed from the list of SargBindings. */ public RexNode getSargBindingListToRexNode( List<SargBinding> sargBindingList) { if (sargBindingList.isEmpty()) { return null; } RexNode newAndNode = sarg2RexMap.get(sargBindingList.get(0).getExpr()); for (int i = 1; i < sargBindingList.size(); i++) { RexNode nextNode = sarg2RexMap.get(sargBindingList.get(i).getExpr()); newAndNode = factory.getRexBuilder().makeCall( SqlStdOperatorTable.AND, newAndNode, nextNode); } return newAndNode; } /** * @deprecated use {@link #getSargBindingListToRexNode(List)} */ public RexNode getResidualSargRexNode(List<SargBinding> residualSargList) { return getSargBindingListToRexNode(residualSargList); } /** * Analyzes a rex predicate. * * @param rexPredicate predicate to be analyzed * @return corresponding bound sarg expression, or null if analysis failed */ public SargBinding analyze(RexNode rexPredicate) { NodeVisitor visitor = new NodeVisitor(); // Initialize analysis state. exprStack = new ArrayList<SargExpr>(); failed = false; boundInputRef = null; clearLeaf(); // Walk the predicate. rexPredicate.accept(visitor); if (boundInputRef == null) { // No variable references at all, so not sargable. failed = true; } if (exprStack.isEmpty()) { failed = true; } if (failed) { return null; } // well-formedness assumption assert exprStack.size() == 1; SargExpr expr = exprStack.get(0); if (!testDynamicParamSupport(expr)) { failed = true; return null; } return new SargBinding(expr, boundInputRef); } private void clearLeaf() { coordinate = null; variableSeen = false; reverse = false; } //~ Inner Classes ---------------------------------------------------------- private abstract class CallConvertlet { public abstract void convert(RexCall call); } private class ComparisonConvertlet extends CallConvertlet { private final SargBoundType boundType; private final SargStrictness strictness; ComparisonConvertlet( SargBoundType boundType, SargStrictness strictness) { this.boundType = boundType; this.strictness = strictness; } // implement CallConvertlet public void convert(RexCall call) { if (!variableSeen) { failed = true; } SqlOperator op = call.getOperator(); switch (op.getKind()) { case IS_NULL: coordinate = factory.getRexBuilder().constantNull(); break; case IS_TRUE: coordinate = factory.getRexBuilder().makeLiteral(true); break; case IS_FALSE: coordinate = factory.getRexBuilder().makeLiteral(false); break; default: if (coordinate == null) { failed = true; } } if (failed) { return; } SargIntervalExpr expr = factory.newIntervalExpr(boundInputRef.getType()); if (boundType == null) { expr.setPoint(coordinate); } else { SargBoundType actualBound = boundType; if (reverse) { if (actualBound == SargBoundType.LOWER) { actualBound = SargBoundType.UPPER; } else { actualBound = SargBoundType.LOWER; } } if (actualBound == SargBoundType.LOWER) { expr.setLower(coordinate, strictness); } else { expr.setUpper(coordinate, strictness); } } exprStack.add(expr); clearLeaf(); } } private class BooleanConvertlet extends CallConvertlet { private final SargSetOperator setOp; BooleanConvertlet(SargSetOperator setOp) { this.setOp = setOp; } // implement CallConvertlet public void convert(RexCall call) { // REVIEW jvs 18-Mar-2006: The test for variableSeen // here and elsewhere precludes predicates like where // (boolean_col AND (int_col > 10)). Should probably // support bare boolean columns as predicates with // coordinate TRUE. if (variableSeen || (coordinate != null)) { failed = true; } if (failed) { return; } int nOperands = call.getOperands().size(); assert exprStack.size() >= nOperands; SargSetExpr expr = factory.newSetExpr( boundInputRef.getType(), setOp); // Pop the correct number of operands off the stack // and transfer them to the new set expression. ListIterator<SargExpr> iter = exprStack.listIterator( exprStack.size() - nOperands); while (iter.hasNext()) { expr.addChild(iter.next()); iter.remove(); } exprStack.add(expr); } } private class NodeVisitor extends RexVisitorImpl<Void> { NodeVisitor() { // go deep super(true); } public Void visitInputRef(RexInputRef inputRef) { boolean coordinate = !isRealRexInputRef(inputRef); if (coordinate) { visitCoordinate(inputRef); return null; } variableSeen = true; if (boundInputRef == null) { boundInputRef = inputRef; return null; } if (inputRef.getIndex() != boundInputRef.getIndex()) { // sargs can only be over a single variable failed = true; return null; } return null; } private boolean isRealRexInputRef(RexInputRef inputRef) { if (lowerRexInputIdx < 0 && upperRexInputIdx < 0) { return true; } int idx = inputRef.getIndex(); return idx < lowerRexInputIdx || idx >= upperRexInputIdx; } public Void visitLiteral(RexLiteral literal) { visitCoordinate(literal); return null; } public Void visitOver(RexOver over) { failed = true; return null; } public Void visitCorrelVariable(RexCorrelVariable correlVariable) { failed = true; return null; } public Void visitCall(RexCall call) { CallConvertlet convertlet = convertletMap.get(call.getOperator()); if (convertlet == null) { failed = true; return null; } // visit operands first super.visitCall(call); convertlet.convert(call); return null; } public Void visitDynamicParam(RexDynamicParam dynamicParam) { if (simpleMode) { failed = true; } else { visitCoordinate(dynamicParam); } return null; } private void visitCoordinate(RexNode node) { if (!variableSeen) { // We may be looking at an expression like (1 < x). reverse = true; } if (coordinate != null) { // e.g. constants on both sides of comparison failed = true; return; } coordinate = node; } public Void visitRangeRef(RexRangeRef rangeRef) { failed = true; return null; } public Void visitFieldAccess(RexFieldAccess fieldAccess) { failed = true; return null; } } } // End SargRexAnalyzer.java