Package org.voltdb.expressions

Source Code of org.voltdb.expressions.ExpressionUtil

/* This file is part of VoltDB.
* Copyright (C) 2008-2014 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.voltdb.expressions;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import org.voltdb.types.ExpressionType;

/**
*
*/
public abstract class ExpressionUtil {

    public static void finalizeValueTypes(AbstractExpression exp)
    {
        exp.normalizeOperandTypes_recurse();
        exp.finalizeValueTypes();
    }

    /**
     *
     * @param exps
     */
    public static AbstractExpression combine(Collection<AbstractExpression> exps) {
        if (exps.isEmpty()) {
            return null;
        }
        Stack<AbstractExpression> stack = new Stack<AbstractExpression>();
        stack.addAll(exps);

        // TODO: This code probably doesn't need to go through all this trouble to create AND trees
        // like "((D and C) and B) and A)" from the list "[A, B, C, D]".
        // There is an easier algorithm that does not require stacking intermediate results.
        // Even better, it would be easier here to generate "(D and (C and (B and A)))"
        // which would also short-circuit slightly faster in the executor.
        // NOTE: Any change to the structure of the trees produced by this algorithm should be
        // reflected in the algorithm used to reverse the process in uncombine(AbstractExpression expr).

        AbstractExpression ret = null;
        while (stack.size() > 1) {
            AbstractExpression child_exp = stack.pop();
            //
            // If our return node is null, then we need to make a new one
            //
            if (ret == null) {
                ret = new ConjunctionExpression(ExpressionType.CONJUNCTION_AND);
                ret.setLeft(child_exp);
            //
            // Check whether we can add it to the right side
            //
            } else if (ret.getRight() == null) {
                ret.setRight(child_exp);
                stack.push(ret);
                ret = null;
            }
        }
        if (ret == null) {
            ret = stack.pop();
        } else {
            ret.setRight(stack.pop());
        }
        return ret;
    }

    /**
     * Undo the effects of the combine(List<AbstractExpression> exps) method to reconstruct the list
     * of expressions in its original order, basically right-to-left in the given expression tree.
     * NOTE: This implementation is tuned to the odd shape of the trees produced by combine,
     * namely leaf-nodes-on-the-right "(((D and C) and B) and A)" from "[A,B,C,D]".
     * Any change there should have a corresponding change here.
     * @param expr
     * @return
     */
    public static List<AbstractExpression> uncombine(AbstractExpression expr)
    {
        if (expr == null) {
            return new ArrayList<AbstractExpression>();
        }
        if (expr instanceof ConjunctionExpression) {
            ConjunctionExpression conj = (ConjunctionExpression)expr;
            if (conj.getExpressionType() == ExpressionType.CONJUNCTION_AND) {
                // Calculate the list for the tree or leaf on the left.
                List<AbstractExpression> branch = uncombine(conj.getLeft());
                // Insert the leaf on the right at the head of that list
                branch.add(0, conj.getRight());
                return branch;
            }
            // Any other kind of conjunction must have been a leaf. Fall through.
        }
        // At the left-most leaf, start a new list.
        List<AbstractExpression> leaf = new ArrayList<AbstractExpression>();
        leaf.add(expr);
        return leaf;
    }

    /**
     * Convert one or more predicates, potentially in an arbitrarily nested conjunction tree
     * into a flattened collection. Similar to uncombine but for arbitrary tree shapes and with no
     * guarantee of the result collection type or of any ordering within the collection.
     * In fact, it currently fills an ArrayDeque via a left=to-right breadth first traversal,
     * but for no particular reason, so that's all subject to change.
     * @param expr
     * @return a Collection containing expr or if expr is a conjunction, its top-level non-conjunction
     * child expressions.
     */
    public static Collection<AbstractExpression> uncombineAny(AbstractExpression expr)
    {
        ArrayDeque<AbstractExpression> out = new ArrayDeque<AbstractExpression>();
        if (expr != null) {
            ArrayDeque<AbstractExpression> in = new ArrayDeque<AbstractExpression>();
            // this chunk of code breaks the code into a list of expression that
            // all have to be true for the where clause to be true
            in.add(expr);
            AbstractExpression inExpr = null;
            while ((inExpr = in.poll()) != null) {
                if (inExpr.getExpressionType() == ExpressionType.CONJUNCTION_AND) {
                    in.add(inExpr.getLeft());
                    in.add(inExpr.getRight());
                }
                else {
                    out.add(inExpr);
                }
            }
        }
        return out;
    }

    public static boolean isColumnEquivalenceFilter(AbstractExpression expr) {
        // Ignore expressions that are not of COMPARE_EQUAL type
        if (expr.getExpressionType() != ExpressionType.COMPARE_EQUAL) {
            return false;
        }
        AbstractExpression leftExpr = expr.getLeft();
        AbstractExpression rightExpr = expr.getRight();
        // Can't use an expression that is based on a column value but is not just a simple column value.
        if ( ( ! (leftExpr instanceof TupleValueExpression)) &&
                leftExpr.hasAnySubexpressionOfClass(TupleValueExpression.class) ) {
            return false;
        }
        if ( ( ! (rightExpr instanceof TupleValueExpression)) &&
                rightExpr.hasAnySubexpressionOfClass(TupleValueExpression.class) ) {
            return false;
        }
        return true;
    }

    /**
     * Find any listed expressions that qualify as potential partitioning where filters,
     * which is to say are equality comparisons with a TupleValueExpression on at least one side,
     * and a TupleValueExpression, ConstantValueExpression, or ParameterValueExpression on the other.
     * Add them to a map keyed by the TupleValueExpression(s) involved.
     * @param filterList a list of candidate expressions
     * @param the running result
     * @return a Collection containing the qualifying filter expressions.
     */
    public static void
    collectPartitioningFilters(Collection<AbstractExpression> filterList,
                               HashMap<AbstractExpression, Set<AbstractExpression> > equivalenceSet)
    {
        for (AbstractExpression expr : filterList) {
            if ( ! isColumnEquivalenceFilter(expr)) {
                continue;
            }
            AbstractExpression leftExpr = expr.getLeft();
            AbstractExpression rightExpr = expr.getRight();

            // Any two asserted-equal expressions need to map to the same equivalence set,
            // which must contain them and must be the only such set that contains them.
            Set<AbstractExpression> eqSet1 = null;
            if (equivalenceSet.containsKey(leftExpr)) {
                eqSet1 = equivalenceSet.get(leftExpr);
            }
            if (equivalenceSet.containsKey(rightExpr)) {
                Set<AbstractExpression> eqSet2 = equivalenceSet.get(rightExpr);
                if (eqSet1 == null) {
                    // Add new leftExpr into existing rightExpr's eqSet.
                    equivalenceSet.put(leftExpr, eqSet2);
                    eqSet2.add(leftExpr);
                } else {
                    // Merge eqSets, re-mapping all the rightExpr's equivalents into leftExpr's eqset.
                    for (AbstractExpression eqMember : eqSet2) {
                        eqSet1.add(eqMember);
                        equivalenceSet.put(eqMember, eqSet1);
                    }
                }
            } else {
                if (eqSet1 == null) {
                    // Both leftExpr and rightExpr are new -- add leftExpr to the new eqSet first.
                    eqSet1 = new HashSet<AbstractExpression>();
                    equivalenceSet.put(leftExpr, eqSet1);
                    eqSet1.add(leftExpr);
                }
                // Add new rightExpr into leftExpr's eqSet.
                equivalenceSet.put(rightExpr, eqSet1);
                eqSet1.add(rightExpr);
            }
        }
    }

    /**
     *
     * @param left
     * @param right
     * @return Both expressions passed in combined by an And conjunction.
     */
    public static AbstractExpression combine(AbstractExpression left, AbstractExpression right) {
        return new ConjunctionExpression(ExpressionType.CONJUNCTION_AND, left, right);
    }

    /**
     * Recursively walk an expression and return a list of all the tuple
     * value expressions it contains.
     */
    public static List<TupleValueExpression>
    getTupleValueExpressions(AbstractExpression input)
    {
        ArrayList<TupleValueExpression> tves =
            new ArrayList<TupleValueExpression>();
        // recursive stopping steps
        if (input == null)
        {
            return tves;
        }
        if (input instanceof TupleValueExpression)
        {
            tves.add((TupleValueExpression) input);
            return tves;
        }

        // recursive calls
        tves.addAll(getTupleValueExpressions(input.m_left));
        tves.addAll(getTupleValueExpressions(input.m_right));
        if (input.m_args != null) {
            for (AbstractExpression argument : input.m_args) {
                tves.addAll(getTupleValueExpressions(argument));
            }
        }
        return tves;
    }

    /**
     * Method to simplify an expression by eliminating identical subexpressions (same id)
     * If the expression is a logical conjunction of the form e1 AND e2 AND e3 AND e4,
     * and subexpression e1 is identical to the subexpression e2 the simplified expression is
     * e1 AND e3 AND e4.
     *
     * @param expr to simplify
     * @return simplified expression.
     */
    public static AbstractExpression eliminateDuplicates(Collection<AbstractExpression> exprList) {
        // Eliminate duplicates by building the map of expression's ids, values.
        Map<String, AbstractExpression> subExprMap = new HashMap<String, AbstractExpression>();
        for (AbstractExpression subExpr : exprList) {
            subExprMap.put(subExpr.m_id, subExpr);
        }
        // Now reconstruct the expression
        ArrayList<AbstractExpression> newList = new ArrayList<AbstractExpression>();
        newList.addAll(subExprMap.values());
        return ExpressionUtil.combine(newList);
    }

    /**
     *  A condition is null-rejected for a given table in the following cases:
     *      If it is of the form A IS NOT NULL, where A is an attribute of any of the inner tables
     *      If it is a predicate containing a reference to an inner table that evaluates to UNKNOWN
     *          when one of its arguments is NULL
     *      If it is a conjunction containing a null-rejected condition as a conjunct
     *      If it is a disjunction of null-rejected conditions
     *
     * @param expr
     * @param tableAlias
     * @return
     */
    public static boolean isNullRejectingExpression(AbstractExpression expr, String tableAlias) {
        ExpressionType exprType = expr.getExpressionType();
        if (exprType == ExpressionType.CONJUNCTION_AND) {
            assert(expr.m_left != null && expr.m_right != null);
            return isNullRejectingExpression(expr.m_left, tableAlias) || isNullRejectingExpression(expr.m_right, tableAlias);
        } else if (exprType == ExpressionType.CONJUNCTION_OR) {
            assert(expr.m_left != null && expr.m_right != null);
            return isNullRejectingExpression(expr.m_left, tableAlias) && isNullRejectingExpression(expr.m_right, tableAlias);
        } else if (exprType == ExpressionType.OPERATOR_NOT) {
            assert(expr.m_left != null);
            // "NOT ( P and Q )" is as null-rejecting as "NOT P or NOT Q"
            // "NOT ( P or Q )" is as null-rejecting as "NOT P and NOT Q"
            // Handling AND and OR expressions requires a "negated" flag to the recursion that tweaks
            // (switches?) the handling of ANDs and ORs to enforce the above equivalences.
            if (expr.m_left.getExpressionType() == ExpressionType.OPERATOR_IS_NULL) {
                return containsMatchingTVE(expr, tableAlias);
            } else if (expr.m_left.getExpressionType() == ExpressionType.CONJUNCTION_AND ||
                    expr.m_left.getExpressionType() == ExpressionType.CONJUNCTION_OR) {
                assert(expr.m_left.m_left != null && expr.m_left.m_right != null);
                // Need to test for an existing child NOT and skip it.
                // e.g. NOT (P AND NOT Q) --> (NOT P) OR NOT NOT Q --> (NOT P) OR Q
                AbstractExpression tempLeft = null;
                if (expr.m_left.m_left.getExpressionType() != ExpressionType.OPERATOR_NOT) {
                    tempLeft = new OperatorExpression(ExpressionType.OPERATOR_NOT, expr.m_left.m_left, null);
                } else {
                    assert(expr.m_left.m_left.m_left != null);
                    tempLeft = expr.m_left.m_left.m_left;
                }
                AbstractExpression tempRight = null;
                if (expr.m_left.m_right.getExpressionType() != ExpressionType.OPERATOR_NOT) {
                    tempRight = new OperatorExpression(ExpressionType.OPERATOR_NOT, expr.m_left.m_right, null);
                } else {
                    assert(expr.m_left.m_right.m_left != null);
                    tempRight = expr.m_left.m_right.m_left;
                }
                ExpressionType type = (expr.m_left.getExpressionType() == ExpressionType.CONJUNCTION_AND) ?
                        ExpressionType.CONJUNCTION_OR : ExpressionType.CONJUNCTION_AND;
                AbstractExpression tempExpr = new OperatorExpression(type, tempLeft, tempRight);
                return isNullRejectingExpression(tempExpr, tableAlias);
            } else if (expr.m_left.getExpressionType() == ExpressionType.OPERATOR_NOT) {
                // It's probably safe to assume that HSQL will have stripped out other double negatives,
                // (like "NOT T.c IS NOT NULL"). Yet, we could also handle them here
                assert(expr.m_left.m_left != null);
                return isNullRejectingExpression(expr.m_left.m_left, tableAlias);
            } else {
                return isNullRejectingExpression(expr.m_left, tableAlias);
            }
        } else if (exprType == ExpressionType.OPERATOR_IS_NULL) {
            // IS NOT NULL is NULL rejecting -- IS NULL is not
            return false;
        } else {
            // @TODO ENG_3038 Is it safe to assume for the rest of the expressions that if
            // it contains a TVE with the matching table name then it is NULL rejection expression?
            // Presently, yes, logical expressions are not expected to appear inside other
            // generalized expressions, so since the handling of other kinds of expressions
            // is pretty much "containsMatchingTVE", this fallback should be safe.
            // The only planned developments that might contradict this restriction (AFAIK --paul)
            // would be support for standard pseudo-functions that take logical condition arguments.
            // These should probably be supported as special non-functions/operations for a number
            // of reasons and may need special casing here.
            return containsMatchingTVE(expr, tableAlias);
        }
    }

    private static boolean containsMatchingTVE(AbstractExpression expr, String tableAlias) {
        assert(expr != null);
        List<TupleValueExpression> tves = getTupleValueExpressions(expr);
        for (TupleValueExpression tve : tves) {
            if (tve.m_tableAlias != null) {
                if (tve.m_tableAlias.equals(tableAlias)) {
                    return true;
                }
            } else if (tve.m_tableName.equals(tableAlias)) {
                return true;
            }
        }
        return false;
    }
}
TOP

Related Classes of org.voltdb.expressions.ExpressionUtil

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.