/* This file is part of VoltDB. * Copyright (C) 2008-2010 VoltDB L.L.C. * * VoltDB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * VoltDB 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltdb.expressions; import java.util.List; import java.util.Stack; import org.voltdb.VoltType; import org.voltdb.planner.PlanColumn; import org.voltdb.planner.PlannerContext; import org.voltdb.types.ExpressionType; import org.voltdb.utils.NotImplementedException; import org.voltdb.utils.Pair; import org.voltdb.utils.VoltTypeUtil; /** * */ public abstract class ExpressionUtil { /** * Convenience method for determining whether an Expression object should have a child * Expression on its RIGHT side. The follow types of Expressions do not need a right child: * OPERATOR_NOT * COMPARISON_IN * AggregageExpression * * @param exp * @return Does this expression need a right expression to be valid? */ public static Boolean needsRightExpression(AbstractExpression exp) { assert(exp != null); return (exp.getExpressionType() != ExpressionType.OPERATOR_NOT && exp.getExpressionType() != ExpressionType.COMPARE_IN && !(exp instanceof AggregateExpression)); } /** * Given an expression, find and convert its NUMERIC literals to * acceptable VoltTypes. Inserts may have expressions that are stand-alone * constant value expressions. In this case, allow the caller to pass * a column type. Otherwise, base the NUMERIC to VoltType conversion * on the other operand's type. */ public static void assignLiteralConstantTypesRecursively(AbstractExpression exp) { assignLiteralConstantTypesRecursively(exp, VoltType.INVALID); } public static void assignLiteralConstantTypesRecursively( AbstractExpression exp, VoltType columnType) { if (exp == null) return; if ((exp instanceof ConstantValueExpression) && (exp.m_valueType == VoltType.NUMERIC)) { assert(columnType != VoltType.INVALID); exp.m_valueType = columnType; exp.m_valueSize = columnType.getLengthInBytesForFixedTypes(); return; } assignLiteralConstantTypes_recurse(exp); } /** * Constant literals have a place-holder type of NUMERIC. These types * need to be converted to DECIMAL or DOUBLE in Volt based on the other * operand's type. */ private static void assignLiteralConstantTypes_recurse(AbstractExpression exp) { // Depth first search for NUMERIC children. if (exp == null) { return; } assignLiteralConstantTypes_recurse(exp.m_left); assignLiteralConstantTypes_recurse(exp.m_right); if (exp.m_left != null && exp.m_left.m_valueType == VoltType.NUMERIC) { assignLiteralConstantType(exp.m_left, exp.m_right); } if (exp.m_right != null && exp.m_right.m_valueType == VoltType.NUMERIC) { assignLiteralConstantType(exp.m_right, exp.m_left); } } /** * Helper function to patch up NUMERIC typed constants. */ private static void assignLiteralConstantType(AbstractExpression literal, AbstractExpression other) { if (other.m_valueType != VoltType.DECIMAL) { literal.m_valueType = VoltType.FLOAT; literal.m_valueSize = VoltType.FLOAT.getLengthInBytesForFixedTypes(); } else { literal.m_valueType = VoltType.DECIMAL; literal.m_valueSize = VoltType.DECIMAL.getLengthInBytesForFixedTypes(); } } public static void assignOutputValueTypesRecursively(AbstractExpression exp) { if (exp == null) return; if (exp.m_left != null) assignOutputValueTypesRecursively(exp.m_left); if (exp.m_right != null) assignOutputValueTypesRecursively(exp.m_right); Pair<VoltType,Integer> exprData = calculateOutputValueTypes(exp); exp.m_valueType = exprData.getFirst(); exp.m_valueSize = exprData.getSecond(); } /** * For a given Expression node, figure out what its output values are * This method only looks at a single node and will not recursively walk through the tree * * @param exp */ public static Pair<VoltType,Integer> calculateOutputValueTypes(AbstractExpression exp) { VoltType retType = VoltType.INVALID; int retSize = 0; // // First get the value types for the left and right children // ExpressionType exp_type = exp.getExpressionType(); AbstractExpression left_exp = exp.getLeft(); AbstractExpression right_exp = exp.getRight(); // ------------------------------- // CONSTANT/NULL/PARAMETER/TUPLE VALUES // If our current expression is a Value node, then the QueryPlanner should have // already figured out our types and there is nothing we need to do here // ------------------------------- if (exp instanceof ConstantValueExpression || exp instanceof NullValueExpression || exp instanceof ParameterValueExpression || exp instanceof TupleValueExpression) { // // Nothing to do... // We have to return what we already have to keep the QueryPlanner happy // retType = exp.getValueType(); retSize = exp.getValueSize(); // ------------------------------- // CONJUNCTION & COMPARISON // If it is an Comparison or Conjunction node, then the output is always // going to be either true or false // ------------------------------- } else if (exp instanceof ComparisonExpression || exp instanceof ConjunctionExpression) { // // Make sure that they have the same number of output values // NOTE: We do not need to do this check for COMPARE_IN // if (exp_type != ExpressionType.COMPARE_IN) { // // IMPORTANT: // We are not handling the case where one of types is NULL. That is because we // are only dealing with what the *output* type should be, not what the actual // value is at execution time. There will need to be special handling code // over on the ExecutionEngine to handle special cases for conjunctions with NULLs // Therefore, it is safe to assume that the output is always going to be an // integer (for booleans) // retType = VoltType.BIGINT; retSize = retType.getLengthInBytesForFixedTypes(); // // Everything else... // } else { // // TODO: Need to figure out how COMPARE_IN is going to work // throw new NotImplementedException("The '" + exp_type + "' Expression is not yet supported"); } // ------------------------------- // AGGREGATES // ------------------------------- } else if (exp instanceof AggregateExpression) { switch (exp_type) { case AGGREGATE_COUNT: case AGGREGATE_COUNT_STAR: // // Always an integer // retType = VoltType.BIGINT; retSize = retType.getLengthInBytesForFixedTypes(); break; case AGGREGATE_AVG: case AGGREGATE_MAX: case AGGREGATE_MIN: // // It's always whatever the base type is // retType = left_exp.getValueType(); retSize = left_exp.getValueSize(); break; case AGGREGATE_SUM: if (left_exp.getValueType() == VoltType.TINYINT || left_exp.getValueType() == VoltType.SMALLINT || left_exp.getValueType() == VoltType.INTEGER) { retType = VoltType.BIGINT; retSize = retType.getLengthInBytesForFixedTypes(); } else { retType = left_exp.getValueType(); retSize = left_exp.getValueSize(); } break; default: throw new RuntimeException("ERROR: Invalid Expression type '" + exp_type + "' for Expression '" + exp + "'"); } // SWITCH // ------------------------------- // EVERYTHING ELSE // We need to look at our children and iterate through their // output value types. We will match up the left and right output types // at each position and call the method to figure out the cast type // ------------------------------- } else { VoltType left_type = left_exp.getValueType(); VoltType right_type = right_exp.getValueType(); VoltType cast_type = VoltType.INVALID; // // If there doesn't need to be a a right expression, then the type will always be a integer (for booleans) // if (!ExpressionUtil.needsRightExpression(exp)) { // // Make sure that they can cast the left-side expression with integer // This is just a simple check to make sure that it is a numeric value // try { // NOTE: in some brave new Decimal world, this check will be // unnecessary. This code path is currently extremely unlikely // anyway since we don't support any of the ways to get here cast_type = VoltType.DECIMAL; if (left_type != VoltType.DECIMAL) { VoltTypeUtil.determineImplicitCasting(left_type, VoltType.BIGINT); cast_type = VoltType.BIGINT; } } catch (Exception ex) { throw new RuntimeException("ERROR: Invalid type '" + left_type + "' used in a '" + exp_type + "' Expression"); } // // Otherwise, use VoltTypeUtil to figure out what to case the value to // } else { cast_type = VoltTypeUtil.determineImplicitCasting(left_type, right_type); } if (cast_type == VoltType.INVALID) { throw new RuntimeException("ERROR: Invalid output value type for Expression '" + exp + "'"); } retType = cast_type; // this may not always be safe retSize = cast_type.getLengthInBytesForFixedTypes(); } return new Pair<VoltType, Integer>(retType, retSize); } /** * Returns true if the provided Expression object is a ConstantValueExpression and its * value will evaluate to true. * @param exp the Expression to check whether it is a * @return true if the exp is a ConstantValueExpression that evaluates to true */ public static Boolean isTrueBooleanExpression(AbstractExpression exp) { return (exp instanceof ConstantValueExpression && Integer.valueOf(((ConstantValueExpression)exp).getValue()) == 1); } /** * * @param exp * @throws Exception */ public static AbstractExpression clone(AbstractExpression exp) throws Exception { AbstractExpression ret = (AbstractExpression)exp.clone(); // if (exp.getLeft() != null) { // ret.setLeft(ExpressionUtil.clone(exp.getLeft())); // } // if (exp.getRight() != null) { // ret.setRight(ExpressionUtil.clone(exp.getRight())); // } return ret; } /** * * @param exps */ public static AbstractExpression combine(List<AbstractExpression> exps) { Stack<AbstractExpression> stack = new Stack<AbstractExpression>(); stack.addAll(exps); if (stack.isEmpty()) { return null; } else if (stack.size() == 1) { return stack.pop(); } 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; } /** * * @param left * @param right * @return Both expressions passed in combined by an And conjunction. */ public static AbstractExpression combine(AbstractExpression left, AbstractExpression right) { AbstractExpression ret = null; ret = new ConjunctionExpression(ExpressionType.CONJUNCTION_AND); ret.setLeft(left); ret.setRight(right); return ret; } public static void setAndOffsetColumnIndexes(PlannerContext context, AbstractExpression input, int offset, String tableName, List<Integer> outputColumns) { // recursive stopping step if (input == null) return; // recursive call setAndOffsetColumnIndexes(context, input.m_left, offset, tableName, outputColumns); setAndOffsetColumnIndexes(context, input.m_right, offset, tableName, outputColumns); // actual work if (input instanceof TupleValueExpression) { TupleValueExpression expr = (TupleValueExpression) input; if (expr.m_tableName.equals(tableName)) { expr.m_columnIndex += offset; } final String exprTableName = expr.getTableName(); final String columnName = expr.getColumnName(); int ii = 0; for (Integer colGuid : outputColumns) { PlanColumn info = context.get(colGuid); if (info.originTableName().equals(exprTableName)) { if (info.getDisplayName().equals(columnName)) { expr.setColumnIndex(ii); return; } } ii++; } } } /** * An AbstractExpression may be applied to a tuple that is a result of several joins and projections. Until the * order of joins and projections is known it is not possible to determine the column index of the value a TupleValueExpression * should return. Once that information is known the expression can be updated to have the correct index. It does this * by searching the list of output columns for the column that originated from the correct table and column name. * @param input The expression to update * @param outputColumns The description of the output columns of the tuple that this expression will be applied to. */ public static void setColumnIndexes(PlannerContext context, AbstractExpression input, List<Integer> outputColumns) { if (input == null) { return; } // recursive call setColumnIndexes(context, input.m_left, outputColumns); setColumnIndexes(context, input.m_right, outputColumns); // actual work if (input instanceof TupleValueExpression) { final TupleValueExpression expr = (TupleValueExpression) input; final String tableName = expr.getTableName(); final String columnName = expr.getColumnName(); int ii = 0; for (Integer colGuid : outputColumns) { PlanColumn info = context.get(colGuid); if (info.originTableName().equals(tableName)) { if (info.getDisplayName().equals(columnName)) { expr.setColumnIndex(ii); return; } } ii++; } } } public static AbstractExpression getOtherTableExpression(AbstractExpression expr, String tableName) { assert(expr != null); AbstractExpression retval = expr.getLeft(); AbstractExpression left = expr.getLeft(); if (left instanceof TupleValueExpression) { TupleValueExpression lv = (TupleValueExpression) left; if (lv.getTableName().equals(tableName)) retval = null; } if (retval == null) { retval = expr.getRight(); AbstractExpression right = expr.getRight(); if (right instanceof TupleValueExpression) { TupleValueExpression rv = (TupleValueExpression) right; if (rv.getTableName().equals(tableName)) retval = null; } } return retval; } }