/* This file is part of VoltDB.
* Copyright (C) 2008-2010 VoltDB L.L.C.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package org.voltdb.expressions;
import java.util.*;
import org.voltdb.VoltType;
import org.voltdb.expressions.AbstractExpression;
import org.voltdb.expressions.ComparisonExpression;
import org.voltdb.expressions.ConjunctionExpression;
import org.voltdb.expressions.ConstantValueExpression;
import org.voltdb.expressions.ExpressionUtil;
import org.voltdb.expressions.ParameterValueExpression;
import org.voltdb.expressions.TupleValueExpression;
import org.voltdb.types.*;
import junit.framework.*;
/**
*
*
*/
public class TestExpressionUtil extends TestCase {
/**
* Used for traversing trees.
*
*/
static abstract class TestExpressionTreeWalker {
/**
* Expression node stack
*/
private final Stack<AbstractExpression> m_stack = new Stack<AbstractExpression>();
/**
* How deep we are in the tree
*/
private int m_depth = -1;
/**
* Depth first traveral
* @param exp
*/
public final void traverse(AbstractExpression exp) {
m_stack.push(exp);
m_depth++;
if (exp.getLeft() != null) {
traverse(exp.getLeft());
}
if (exp.getRight() != null) {
traverse(exp.getRight());
}
AbstractExpression check_exp = m_stack.pop();
assert(exp.equals(check_exp));
callback(exp);
m_depth--;
}
/**
* Returns the parent of the current node in callback()
* @return
*/
protected final AbstractExpression getParent() {
AbstractExpression ret = null;
if (!m_stack.isEmpty()) ret = m_stack.peek();
return ret;
}
/**
* Returns the depth of the current callback() invocation
* @return
*/
protected final int getDepth() {
return m_depth;
}
/**
* This method will be called after the walker has explored a node's children
* @param exp the Expression object to perform an operation on
*/
public abstract void callback(AbstractExpression exp);
}
//
// AND
// / \
// EQUAL NOT
// / \ |
// P T C
//
protected static final AbstractExpression ROOT_EXP = new ConjunctionExpression(ExpressionType.CONJUNCTION_AND);
protected static final AbstractExpression CHILD_EXPS[] = { new ComparisonExpression(ExpressionType.COMPARE_EQUAL),
new ComparisonExpression(ExpressionType.OPERATOR_NOT),
new ParameterValueExpression(),
new TupleValueExpression(),
new ConstantValueExpression()
};
static {
ROOT_EXP.setLeft(CHILD_EXPS[0]);
ROOT_EXP.setRight(CHILD_EXPS[1]);
CHILD_EXPS[0].setLeft(CHILD_EXPS[2]);
CHILD_EXPS[0].setRight(CHILD_EXPS[3]);
CHILD_EXPS[1].setLeft(CHILD_EXPS[4]);
//ExpressionUtil.generateIds(ROOT_EXP);
} // STATIC
// ------------------------------------------------------------------
// COMPARISON METHODS
// ------------------------------------------------------------------
protected static void compareExpressionTrees(AbstractExpression out_exp, AbstractExpression in_exp) {
//
// Make sure that compacted tree is exactly the same as the original
// sub-tree that should have been preserved
//
final Vector<AbstractExpression> orig_list = new Vector<AbstractExpression>();
new TestExpressionTreeWalker() {
@Override
public void callback(AbstractExpression exp) {
orig_list.add(exp);
}
}.traverse(out_exp);
assertFalse(orig_list.isEmpty());
new TestExpressionTreeWalker() {
@Override
public void callback(AbstractExpression exp) {
assertFalse(orig_list.isEmpty());
AbstractExpression pop_exp = orig_list.remove(0);
TestExpressionUtil.compareExpressions(pop_exp, exp);
}
}.traverse(in_exp);
}
protected static void compareExpressions(AbstractExpression out_exp, AbstractExpression in_exp) {
//
// ID
//
/*if (out_exp.getId() != null) {
assertNotNull(in_exp.getId());
if (!out_exp.getId().equals(in_exp.getId())) {
System.err.println("OUT: " + out_exp);
System.err.println("IN: " + in_exp);
}
assertEquals(out_exp.getId(), in_exp.getId());
} else {
assertNull(in_exp.getId());
}*/
//
// LEFT & RIGHT
//
assertEquals((out_exp.getLeft() == null), (in_exp.getLeft() == null));
assertEquals((out_exp.getRight() == null), (in_exp.getRight() == null));
//
// VALUE TYPE
//
if (out_exp.getValueType() != in_exp.getValueType()) {
System.err.println("OUT: " + out_exp.getValueType());
System.err.println("IN: " + in_exp.getValueType());
}
assertEquals(out_exp.getValueType(), in_exp.getValueType());
//
// Specialized Checks
//
switch (out_exp.getExpressionType()) {
case VALUE_CONSTANT: {
ConstantValueExpression out_const_exp = (ConstantValueExpression)out_exp;
ConstantValueExpression in_const_exp = (ConstantValueExpression)in_exp;
assertEquals(out_const_exp.getValue(), in_const_exp.getValue());
break;
}
case VALUE_PARAMETER:
ParameterValueExpression out_param_exp = (ParameterValueExpression)out_exp;
ParameterValueExpression in_param_exp = (ParameterValueExpression)in_exp;
assertEquals(out_param_exp.getParameterId(), in_param_exp.getParameterId());
break;
case VALUE_TUPLE:
TupleValueExpression out_tuple_exp = (TupleValueExpression)out_exp;
TupleValueExpression in_tuple_exp = (TupleValueExpression)in_exp;
assertEquals(out_tuple_exp.getColumnIndex(), in_tuple_exp.getColumnIndex());
break;
} // SWITCH
return;
}
// ------------------------------------------------------------------
// TEST CASES
// ------------------------------------------------------------------
/**
* Clone Sub-Tree
*/
public void testClone() {
AbstractExpression cloned_exp = null;
try {
cloned_exp = ExpressionUtil.clone(ROOT_EXP);
} catch (Exception ex) {
ex.printStackTrace();
}
assertNotNull(cloned_exp);
//
// First build our lists of information about each node in the orignal tree
// It is assumed that the tree will be traversed in the same order
//
final ArrayList<AbstractExpression> orig_exps = new ArrayList<AbstractExpression>();
new TestExpressionTreeWalker() {
@Override
public void callback(AbstractExpression exp) {
orig_exps.add(exp);
}
}.traverse(ROOT_EXP);
//
// Then walk through the cloned tree and make sure the objects are different
// but the information is the same
//
new TestExpressionTreeWalker() {
@Override
public void callback(AbstractExpression exp) {
assertFalse(orig_exps.isEmpty());
AbstractExpression orig_exp = orig_exps.remove(0);
//
// We want to make sure that the cloned Expression doesn't
// have the its ID set to its hashCode
//
assertTrue(orig_exp.hashCode() != exp.hashCode());
//assertFalse(orig_exp.getId().equals(Integer.toString(exp.hashCode())));
//
// Use our general comparison method to check other things
//
TestExpressionUtil.compareExpressions(orig_exp, exp);
}
}.traverse(cloned_exp);
}
/**
*
*
*/
public void testCombine() {
//
// We create a bunch of individual ComparisonExpression trees and we then
// combine them into a single tree created with AND conjunctions
//
int num_of_subtrees = 5;
final List<AbstractExpression> combine_exps = new ArrayList<AbstractExpression>();
final Map<AbstractExpression, AbstractExpression> combine_exps_left = new HashMap<AbstractExpression, AbstractExpression>();
final Map<AbstractExpression, AbstractExpression> combine_exps_right = new HashMap<AbstractExpression, AbstractExpression>();
for (int ctr = 0; ctr < num_of_subtrees; ctr++) {
AbstractExpression exps[] = { new ComparisonExpression(ExpressionType.COMPARE_EQUAL),
new ParameterValueExpression(),
new TupleValueExpression()
};
exps[0].setLeft(exps[1]);
exps[0].setRight(exps[2]);
//ExpressionUtil.generateIds(exps[0]);
combine_exps.add(exps[0]);
combine_exps_left.put(exps[0], exps[1]);
combine_exps_right.put(exps[0], exps[2]);
} // FOR
AbstractExpression combined_exp = null;
try {
combined_exp = ExpressionUtil.combine(combine_exps);
} catch (Exception ex) {
ex.printStackTrace();
}
assertNotNull(combined_exp);
assertEquals(combined_exp.getExpressionType(), ExpressionType.CONJUNCTION_AND);
//System.err.println(combined_exp.toString(true));
//
// Checking whether this worked is a bit tricky because the ordering of the may
// be different if the implementation changges. So we just need to check to make
// sure that all of our sub-trees are contained within the new tree and that their
// structure has not changed
//
new TestExpressionTreeWalker() {
@Override
public void callback(AbstractExpression exp) {
//
// This node was in our original tree
//
if (combine_exps.contains(exp)) {
assertTrue(combine_exps_left.containsKey(exp));
TestExpressionUtil.compareExpressions(exp.getLeft(), combine_exps_left.get(exp));
assertTrue(combine_exps_right.containsKey(exp));
TestExpressionUtil.compareExpressions(exp.getRight(), combine_exps_right.get(exp));
//
// Make sure our parent is a CONJUNCTION_AND expression node
//
assertNotNull(this.getParent());
assertEquals(this.getParent().getExpressionType(), ExpressionType.CONJUNCTION_AND);
//
// If this is a CONJUNCTION_AND that we added, make sure that both of its
// children are not null
//
} else if (exp.getExpressionType() == ExpressionType.CONJUNCTION_AND) {
assertNotNull(exp.getLeft());
assertNotNull(exp.getRight());
}
}
}.traverse(combined_exp);
}
// This is basically just a check that the rules in
// VoltTypeUtil.determineImplicitCasting() push up through the aggregate
// expression properly. We'll just do a few of the corner cases and
// call it good.
public void testAssignOutputValueTypesRecursivelyForAggregateAvg()
{
AbstractExpression root =
new AggregateExpression(ExpressionType.AGGREGATE_AVG);
AbstractExpression op = new TupleValueExpression();
root.setLeft(op);
// Simple tuple value type gets pushed through
op.setValueType(VoltType.FLOAT);
ExpressionUtil.assignOutputValueTypesRecursively(root);
assertEquals(VoltType.FLOAT, root.getValueType());
op.setValueType(VoltType.INTEGER);
ExpressionUtil.assignOutputValueTypesRecursively(root);
assertEquals(VoltType.INTEGER, root.getValueType());
op.setValueType(VoltType.DECIMAL);
ExpressionUtil.assignOutputValueTypesRecursively(root);
assertEquals(VoltType.DECIMAL, root.getValueType());
op = new OperatorExpression();
root.setLeft(op);
AbstractExpression left = new TupleValueExpression();
AbstractExpression right = new TupleValueExpression();
op.setLeft(left);
op.setRight(right);
// FLOAT + int type gets promoted to FLOAT
left.setValueType(VoltType.FLOAT);
right.setValueType(VoltType.INTEGER);
ExpressionUtil.assignOutputValueTypesRecursively(root);
assertEquals(VoltType.FLOAT, root.getValueType());
// random INT types get promoted to BIGINT
left.setValueType(VoltType.TINYINT);
right.setValueType(VoltType.INTEGER);
ExpressionUtil.assignOutputValueTypesRecursively(root);
assertEquals(VoltType.BIGINT, root.getValueType());
// DECIMAL works, at least
left.setValueType(VoltType.DECIMAL);
right.setValueType(VoltType.DECIMAL);
ExpressionUtil.assignOutputValueTypesRecursively(root);
assertEquals(VoltType.DECIMAL, root.getValueType());
}
/** Base case test of NUMERIC literal processing */
public void testAssignLiteralConstantTypes()
{
AbstractExpression lit_dec;
AbstractExpression dec_lit;
AbstractExpression lit;
AbstractExpression dec;
AbstractExpression bint;
// convert NUMERIC to DECIMAL right/left
lit = new ConstantValueExpression();
lit.m_valueType = VoltType.NUMERIC;
lit.m_valueSize = VoltType.NUMERIC.getLengthInBytesForFixedTypes();
dec = new ConstantValueExpression();
dec.m_valueType = VoltType.DECIMAL;
dec.m_valueSize = VoltType.DECIMAL.getLengthInBytesForFixedTypes();
lit_dec = new OperatorExpression(ExpressionType.OPERATOR_PLUS, lit, dec);
ExpressionUtil.assignLiteralConstantTypesRecursively(lit_dec, VoltType.STRING);
assertEquals(lit.m_valueType, VoltType.DECIMAL);
assertEquals(lit.m_valueSize, VoltType.DECIMAL.getLengthInBytesForFixedTypes());
assertEquals(dec.m_valueType, VoltType.DECIMAL);
assertEquals(dec.m_valueSize, VoltType.DECIMAL.getLengthInBytesForFixedTypes());
// convert NUMERIC to DECIMAL (left/right)
lit = new ConstantValueExpression();
lit.m_valueType = VoltType.NUMERIC;
lit.m_valueSize = VoltType.NUMERIC.getLengthInBytesForFixedTypes();
dec = new ConstantValueExpression();
dec.m_valueType = VoltType.DECIMAL;
dec.m_valueSize = VoltType.DECIMAL.getLengthInBytesForFixedTypes();
dec_lit = new OperatorExpression(ExpressionType.OPERATOR_DIVIDE, dec, lit);
ExpressionUtil.assignLiteralConstantTypesRecursively(dec_lit, VoltType.STRING);
assertEquals(lit.m_valueType, VoltType.DECIMAL);
assertEquals(lit.m_valueSize, VoltType.DECIMAL.getLengthInBytesForFixedTypes());
assertEquals(dec.m_valueType, VoltType.DECIMAL);
assertEquals(dec.m_valueSize, VoltType.DECIMAL.getLengthInBytesForFixedTypes());
// convert numeric to float
lit = new ConstantValueExpression();
lit.m_valueType = VoltType.NUMERIC;
lit.m_valueSize = VoltType.NUMERIC.getLengthInBytesForFixedTypes();
bint = new ConstantValueExpression();
bint.m_valueType = VoltType.BIGINT;
bint.m_valueSize = VoltType.BIGINT.getLengthInBytesForFixedTypes();
AbstractExpression lit_bint =
new OperatorExpression(ExpressionType.OPERATOR_MINUS, lit, bint);
ExpressionUtil.assignLiteralConstantTypesRecursively(lit_bint, VoltType.STRING);
assertEquals(lit.m_valueType, VoltType.FLOAT);
assertEquals(lit.m_valueSize, VoltType.FLOAT.getLengthInBytesForFixedTypes());
assertEquals(bint.m_valueType, VoltType.BIGINT);
assertEquals(bint.m_valueSize, VoltType.BIGINT.getLengthInBytesForFixedTypes());
// test a larger tree
lit = new ConstantValueExpression();
lit.m_valueType = VoltType.NUMERIC;
lit.m_valueSize = VoltType.NUMERIC.getLengthInBytesForFixedTypes();
bint = new ConstantValueExpression();
bint.m_valueType = VoltType.DECIMAL;
bint.m_valueSize = VoltType.DECIMAL.getLengthInBytesForFixedTypes();
lit_bint = new OperatorExpression(ExpressionType.OPERATOR_MINUS, lit, bint);
AbstractExpression root = new OperatorExpression(ExpressionType.OPERATOR_MULTIPLY,
lit_bint, new TupleValueExpression());
ExpressionUtil.assignLiteralConstantTypesRecursively(root);
assertEquals(lit.m_valueType, VoltType.DECIMAL);
assertEquals(lit.m_valueSize, VoltType.DECIMAL.getLengthInBytesForFixedTypes());
assertEquals(bint.m_valueType, VoltType.DECIMAL);
assertEquals(bint.m_valueSize, VoltType.DECIMAL.getLengthInBytesForFixedTypes());
}
}