/* This file is part of VoltDB. * Copyright (C) 2008-2017 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.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import org.hsqldb_voltpatches.FunctionSQL; import org.json_voltpatches.JSONArray; import org.json_voltpatches.JSONException; import org.json_voltpatches.JSONObject; import org.json_voltpatches.JSONString; import org.json_voltpatches.JSONStringer; import org.voltdb.VoltType; import org.voltdb.catalog.Table; import org.voltdb.planner.ParsedColInfo; import org.voltdb.planner.parseinfo.StmtTableScan; import org.voltdb.types.ExpressionType; import org.voltdb.types.SortDirectionType; /** * @param <aeClass> * */ public abstract class AbstractExpression implements JSONString, Cloneable { protected static class Members { static final String TYPE = "TYPE"; static final String LEFT = "LEFT"; static final String RIGHT = "RIGHT"; static final String VALUE_TYPE = "VALUE_TYPE"; static final String VALUE_SIZE = "VALUE_SIZE"; static final String IN_BYTES = "IN_BYTES"; static final String ARGS = "ARGS"; } private static class SortMembers { static final String SORT_COLUMNS = "SORT_COLUMNS"; static final String SORT_EXPRESSION = "SORT_EXPRESSION"; static final String SORT_DIRECTION = "SORT_DIRECTION"; } protected String m_id; protected ExpressionType m_type; protected AbstractExpression m_left = null; protected AbstractExpression m_right = null; protected List<AbstractExpression> m_args = null; // Never includes left and right "operator args". protected VoltType m_valueType = null; protected int m_valueSize = 0; protected boolean m_inBytes = false; /** * We set this to non-null iff the expression has a non-deterministic * operation. The most common kind of non-deterministic operation is an * aggregate function applied to a floating point expression. */ private String m_contentDeterminismMessage = null; /** * Note that this expression is inherently non-deterministic. This may be * called if the expression is already known to be non-deterministic, even * if the value is false, because we are careful to never go from true to * false here. Perhaps we should concatenate the messages. But since we only * have one now it would result in unnecessary duplication. * * @param value */ public void updateContentDeterminismMessage(String value) { if (m_contentDeterminismMessage == null) { m_contentDeterminismMessage = value; } } /** * Get the inherent non-determinism state of this expression. This is not * valid before finalizeValueTypes is called. * * @return The state. */ public String getContentDeterminismMessage() { return m_contentDeterminismMessage; } // Keep this flag turned off in production or when testing user-accessible EXPLAIN output or when // using EXPLAIN output to validate plans. protected static boolean m_verboseExplainForDebugging = false; // CODE REVIEWER! this SHOULD be false! public static void enableVerboseExplainForDebugging() { m_verboseExplainForDebugging = true; } public static boolean disableVerboseExplainForDebugging() { boolean was = m_verboseExplainForDebugging; m_verboseExplainForDebugging = false; return was; } public static void restoreVerboseExplainForDebugging(boolean was) { m_verboseExplainForDebugging = was; } /** This is needed for serialization **/ public AbstractExpression() { } public AbstractExpression(ExpressionType type) { m_type = type; } public AbstractExpression(ExpressionType type, AbstractExpression left, AbstractExpression right) { this(type); m_left = left; m_right = right; } public void validate() throws Exception { // // Validate our children first // if (m_left != null) { m_left.validate(); } if (m_right != null) { m_right.validate(); } if (m_args != null) { for (AbstractExpression argument : m_args) { argument.validate(); } } // // Expression Type // if (m_type == null) { throw new Exception("ERROR: The ExpressionType for '" + this + "' is NULL"); } if (m_type == ExpressionType.INVALID) { throw new Exception("ERROR: The ExpressionType for '" + this + "' is " + m_type); } // // Output Type // if (m_valueType == null) { throw new Exception("ERROR: The output VoltType for '" + this + "' is NULL"); } if (m_valueType == VoltType.INVALID) { throw new Exception("ERROR: The output VoltType for '" + this + "' is " + m_valueType); } // // Since it is possible for an AbstractExpression to be stored with // any ExpressionType, we do a simple check to make sure that it is the right class // Class<?> check_class = m_type.getExpressionClass(); if (!check_class.isInstance(this)) { throw new Exception("ERROR: Expression '" + this + "' is class type '" + getClass().getSimpleName() + "' but needs to be '" + check_class.getSimpleName() + "'"); } } @Override public AbstractExpression clone() { AbstractExpression clone; try { clone = (AbstractExpression)super.clone(); } catch (CloneNotSupportedException e) { // umpossible return null; } if (m_left != null) { AbstractExpression left_clone = m_left.clone(); clone.m_left = left_clone; } if (m_right != null) { AbstractExpression right_clone = m_right.clone(); clone.m_right = right_clone; } if (m_args != null) { clone.m_args = new ArrayList<>(); for (AbstractExpression argument : m_args) { clone.m_args.add(argument.clone()); } } return clone; } /** * @return the type */ public ExpressionType getExpressionType() { return m_type; } /** * @param type */ public void setExpressionType(ExpressionType type) { m_type = type; } /** * @return the left */ public AbstractExpression getLeft() { return m_left; } /** * @param left the left to set */ public void setLeft(AbstractExpression left) { m_left = left; } /** * @return the right */ public AbstractExpression getRight() { return m_right; } /** * @param right the right to set */ public void setRight(AbstractExpression right) { m_right = right; } /** * * @return the list of args */ public List<AbstractExpression> getArgs () { return m_args; } /** * @param arguments to set */ public void setArgs(List<AbstractExpression> arguments) { m_args = arguments; } /** * Update the argument at specified index * @param index the index of the item to replace * @param arg the new argument to insert into the list */ public void setArgAtIndex(int index, AbstractExpression arg) { m_args.set(index, arg); } /** * @return The type of this expression's value. */ public VoltType getValueType() { return m_valueType; } /** * @param type The type of this expression's value. */ public void setValueType(VoltType type) { m_valueType = type; } /** * @return The size of this expression's value in bytes. */ public int getValueSize() { return m_valueSize; } /** * @param size The size of this expression's value in bytes. */ public void setValueSize(int size) { assert (size >= 0); assert (size <= 10000000); m_valueSize = size; } public boolean getInBytes() { return m_inBytes; } public void setInBytes(boolean inBytes) { m_inBytes = inBytes; } @Override public String toString() { StringBuilder sb = new StringBuilder(); toStringHelper("", sb); return sb.toString(); } private static final String INDENT = " | "; /** * Return a node name to help out toString. Subclasses * can chime in if they have a notion to. See TupleValueExpression, * for example. */ protected String getExpressionNodeNameForToString() { return getClass().getSimpleName(); } private void toStringHelper(String linePrefix, StringBuilder sb) { String header = getExpressionNodeNameForToString() + " [" + getExpressionType().toString() + "] : "; if (m_valueType != null) { header += m_valueType.toSQLString(); } else { header += "[null type]"; } sb.append(linePrefix + header + "\n"); if (m_left != null) { sb.append(linePrefix + "Left:\n"); m_left.toStringHelper(linePrefix + INDENT, sb); } if (m_right != null) { sb.append(linePrefix + "Right:\n"); m_right.toStringHelper(linePrefix + INDENT, sb); } if (m_args != null) { sb.append(linePrefix + "Args:\n"); for (AbstractExpression arg : m_args) { arg.toStringHelper(linePrefix + INDENT, sb); } } } @Override public boolean equals(Object obj) { if (obj instanceof AbstractExpression == false) { return false; } AbstractExpression expr = (AbstractExpression) obj; if (m_type != expr.m_type) { return false; } if ( ! hasEqualAttributes(expr)) { return false; } // The derived classes have verified that any added attributes are identical. // Check that the presence, or lack, of children is the same if ((m_left == null) != (expr.m_left == null)) { return false; } if ((m_right == null) != (expr.m_right == null)) { return false; } if ((m_args == null) != (expr.m_args == null)) { return false; } // Check that the children identify themselves as equal if (expr.m_left != null) { if (expr.m_left.equals(m_left) == false) { return false; } } if (expr.m_right != null) { if (expr.m_right.equals(m_right) == false) { return false; } } if (expr.m_args != null) { if (expr.m_args.equals(m_args) == false) { return false; } } return true; } /** * Deserialize 2 lists of AbstractExpressions from JSON format strings * and determine whether the expressions at each position are equal. * The issue that must be worked around is that json format string equality * is sensitive to the valuetype and valuesize of each expression and its * subexpressions, but AbstractExpression.equals is not * -- overloaded abstract expressions are equal, for example, * two overloads of the same function or operator applied to columns * with the same name but different types. * These overloads are sometimes allowable in live schema updates where * more general changes might be forbidden. * @return true iff the two strings represent lists of the same expressions, * allowing for value type differences */ public static boolean areOverloadedJSONExpressionLists( String jsontext1, String jsontext2) { try { List<AbstractExpression> list1 = fromJSONArrayString(jsontext1, null); List<AbstractExpression> list2 = fromJSONArrayString(jsontext2, null); return list1.equals(list2); } catch (JSONException je) { return false; } } // Derived classes that define attributes should compare them in their // refinements of this method. // This implementation is provided as a convenience for Operators et. al. // that have no attributes that could differ. protected boolean hasEqualAttributes(AbstractExpression expr) { return true; } // A check for "structural similarity" to an indexed expression that // generally uses equality between the expression trees but also matches a // ParameterValueExpression having an "original value" constant // in the LHS to an equal ConstantValueExpression within the RHS // -- that's actually taken care of by delegation to the // ParameterValueExpression override of this function. // @return - null if there is no match, // otherwise a list of "bound parameters" used by the match, // possibly an empty list if the found match was based on // strict expression equality that didn't involve parameters. public List<AbstractExpression> bindingToIndexedExpression( AbstractExpression expr) { // Defer the result construction for as long as possible on the // assumption that this function mostly gets applied to eliminate // negative cases. if (m_type != expr.m_type) { // The only allowed difference in expression types is between a // parameter and its original constant value. // That's handled in the independent override. return null; } // From here, this is much like the straight equality check, // except that this function and "equals" must each call themselves // in their recursions. // Delegating to this factored-out component of the "equals" // implementation eases simultaneous refinement of both methods. if ( ! hasEqualAttributes(expr)) { return null; } // The derived classes have verified that any added attributes // are identical. // Check that the presence, or lack, of children is the same if ((expr.m_left == null) != (m_left == null)) { return null; } if ((expr.m_right == null) != (m_right == null)) { return null; } if ((expr.m_args == null) != (m_args == null)) { return null; } // Check that the children identify themselves as matching List<AbstractExpression> leftBindings = null; if (m_left != null) { leftBindings = m_left.bindingToIndexedExpression(expr.m_left); if (leftBindings == null) { return null; } } List<AbstractExpression> rightBindings = null; if (m_right != null) { rightBindings = m_right.bindingToIndexedExpression(expr.m_right); if (rightBindings == null) { return null; } } List<AbstractExpression> argBindings = null; if (m_args != null) { if (m_args.size() != expr.m_args.size()) { return null; } argBindings = new ArrayList<>(); int ii = 0; // iterate the args lists in parallel, binding pairwise for (AbstractExpression rhs : expr.m_args) { AbstractExpression lhs = m_args.get(ii++); List<AbstractExpression> moreBindings = lhs.bindingToIndexedExpression(rhs); if (moreBindings == null) { // fail on any non-match return null; } argBindings.addAll(moreBindings); } } // It's a match, so gather up the details. // It's rare (if even possible) for the same bound parameter to get // listed twice, so don't worry about duplicate entries, here. // That should not cause any issue for the caller. List<AbstractExpression> result = new ArrayList<>(); if (leftBindings != null) { // null here can only mean no left child result.addAll(leftBindings); } if (rightBindings != null) { // null here can only mean no right child result.addAll(rightBindings); } if (argBindings != null) { // null here can only mean no args result.addAll(argBindings); } return result; } @Override public int hashCode() { // based on implementation of equals int result = 0; // hash the children if (m_left != null) { result += m_left.hashCode(); } if (m_right != null) { result += m_right.hashCode(); } if (m_args != null) { result += m_args.hashCode(); } if (m_type != null) { result += m_type.hashCode(); } return result; } @Override public String toJSONString() { JSONStringer stringer = new JSONStringer(); try { stringer.object(); toJSONString(stringer); stringer.endObject(); } catch (JSONException e) { e.printStackTrace(); return null; } return stringer.toString(); } public void toJSONString(JSONStringer stringer) throws JSONException { stringer.keySymbolValuePair(Members.TYPE, m_type.getValue()); if (m_valueType == null) { stringer.keySymbolValuePair(Members.VALUE_TYPE, VoltType.NULL.getValue()); stringer.keySymbolValuePair(Members.VALUE_SIZE, m_valueSize); } else { stringer.keySymbolValuePair(Members.VALUE_TYPE, m_valueType.getValue()); if (m_valueType.getLengthInBytesForFixedTypesWithoutCheck() == -1) { stringer.keySymbolValuePair(Members.VALUE_SIZE, m_valueSize); } if (m_inBytes) { assert(m_valueType == VoltType.STRING); stringer.keySymbolValuePair(Members.IN_BYTES, true); } } if (m_left != null) { stringer.key(Members.LEFT).value(m_left); } if (m_right != null) { stringer.key(Members.RIGHT).value(m_right); } if (m_args != null) { stringer.key(Members.ARGS).array(m_args); } } /** * Given a JSONStringer and a sequence of sort expressions and directions, * serialize the sort expressions. These will be in an array which is * the value of SortMembers.SORT_COLUMNS in the current object of * the JSONString. The JSONString should be in object state, not * array state. * * @param stringer The stringer used to serialize the sort list. * @param sortExpressions The sort expressions. * @param sortDirections The sort directions. These may be empty if the * directions are not valueable to us. * @throws JSONException */ public static void toJSONArrayFromSortList( JSONStringer stringer, List<AbstractExpression> sortExpressions, List<SortDirectionType> sortDirections) throws JSONException { stringer.key(SortMembers.SORT_COLUMNS); stringer.array(); int listSize = sortExpressions.size(); for (int ii = 0; ii < listSize; ii++) { stringer.object(); stringer.key(SortMembers.SORT_EXPRESSION).object(); sortExpressions.get(ii).toJSONString(stringer); stringer.endObject(); if (sortDirections != null) { stringer.keySymbolValuePair(SortMembers.SORT_DIRECTION, sortDirections.get(ii).toString()); } stringer.endObject(); } stringer.endArray(); } protected void loadFromJSONObject(JSONObject obj) throws JSONException { } protected void loadFromJSONObject(JSONObject obj, StmtTableScan tableScan) throws JSONException { loadFromJSONObject(obj); } public static AbstractExpression fromJSONChild( JSONObject jobj, String label) throws JSONException { if (jobj.isNull(label)) { return null; } return fromJSONObject(jobj.getJSONObject(label), null); } public static AbstractExpression fromJSONChild( JSONObject jobj, String label, StmtTableScan tableScan) throws JSONException { if (jobj.isNull(label)) { return null; } return fromJSONObject(jobj.getJSONObject(label), tableScan); } private static AbstractExpression fromJSONObject( JSONObject obj, StmtTableScan tableScan) throws JSONException { ExpressionType type = ExpressionType.get(obj.getInt(Members.TYPE)); AbstractExpression expr; try { expr = type.getExpressionClass().newInstance(); } catch (InstantiationException e) { e.printStackTrace(); return null; } catch (IllegalAccessException e) { e.printStackTrace(); return null; } expr.m_type = type; expr.m_valueType = VoltType.get((byte) obj.getInt(Members.VALUE_TYPE)); if (obj.has(Members.VALUE_SIZE)) { expr.m_valueSize = obj.getInt(Members.VALUE_SIZE); } else { expr.m_valueSize = expr.m_valueType.getLengthInBytesForFixedTypes(); } expr.m_left = fromJSONChild(obj, Members.LEFT, tableScan); expr.m_right = fromJSONChild(obj, Members.RIGHT, tableScan); if ( ! obj.isNull(Members.ARGS)) { JSONArray jarray = obj.getJSONArray(Members.ARGS); ArrayList<AbstractExpression> arguments = new ArrayList<>(); loadFromJSONArray(arguments, jarray, tableScan); expr.setArgs(arguments); } expr.loadFromJSONObject(obj, tableScan); return expr; } /** * Load two lists from a JSONObject. * One list is for sort expressions and the other is for sort directions. * The lists are cleared before they are filled in. * This is the inverse of toJSONArrayFromSortList. * * The JSONObject should be in object state, not array state. * It should have a member named SORT_COLUMNS, * which is an array with the <expression, direction> pairs. * Sometimes the sort directions are not needed. * For example, when deserializing * * @param sortExpressions The container for the sort expressions. * @param sortDirections The container for the sort directions. This may * be null if we don't care about directions. If there * are no directions in the list this will be empty. * @param jarray * @throws JSONException */ public static void loadSortListFromJSONArray( List<AbstractExpression> sortExpressions, List<SortDirectionType> sortDirections, JSONObject jobj) throws JSONException { if (jobj.has(SortMembers.SORT_COLUMNS)) { sortExpressions.clear(); if (sortDirections != null) { sortDirections.clear(); } JSONArray jarray = jobj.getJSONArray(SortMembers.SORT_COLUMNS); int size = jarray.length(); for (int ii = 0; ii < size; ++ii) { JSONObject tempObj = jarray.getJSONObject(ii); sortExpressions.add( fromJSONChild(tempObj, SortMembers.SORT_EXPRESSION)); if (sortDirections == null || ! tempObj.has(SortMembers.SORT_DIRECTION)) { continue; } String sdAsString = tempObj.getString(SortMembers.SORT_DIRECTION); sortDirections.add(SortDirectionType.get(sdAsString)); } } assert(sortDirections == null || sortExpressions.size() == sortDirections.size()); } public static List<AbstractExpression> fromJSONArrayString( String jsontext, StmtTableScan tableScan) throws JSONException { JSONArray jarray = new JSONArray(jsontext); List<AbstractExpression> result = new ArrayList<>(); loadFromJSONArray(result, jarray, tableScan); return result; } public static void fromJSONArrayString(String jsontext, StmtTableScan tableScan, List<AbstractExpression> result) throws JSONException { result.addAll(fromJSONArrayString(jsontext, tableScan)); } public static AbstractExpression fromJSONString( String jsontext, StmtTableScan tableScan) throws JSONException { JSONObject jobject = new JSONObject(jsontext); return fromJSONObject(jobject, tableScan); } /** * For TVEs, it is only serialized column index and table index. * In order to match expression, * there needs more information to revert back the table name, * table alisa and column name. * By adding @param tableScan, the TVE will load table name, * table alias and column name for TVE. * @param starter * @param parent * @param label * @param tableScan * @throws JSONException */ public static List<AbstractExpression> loadFromJSONArrayChild( List<AbstractExpression> starter, JSONObject parent, String label, StmtTableScan tableScan) throws JSONException { if (parent.isNull(label)) { return null; } JSONArray jarray = parent.getJSONArray(label); return loadFromJSONArray(starter, jarray, tableScan); } private static List<AbstractExpression> loadFromJSONArray( List<AbstractExpression> starter, JSONArray jarray, StmtTableScan tableScan) throws JSONException { if (starter == null) { starter = new ArrayList<>(); } int size = jarray.length(); for (int i = 0 ; i < size; ++i) { JSONObject tempjobj = jarray.getJSONObject(i); starter.add(fromJSONObject(tempjobj, tableScan)); } return starter; } /** * This function recursively replaces any subexpression matching an entry in * aggTableIndexMap with an equivalent TVE. * Its column index and alias are also built up here. * @param aggTableIndexMap * @param indexToColumnMap * @return */ public AbstractExpression replaceWithTVE( Map<AbstractExpression, Integer> aggTableIndexMap, Map<Integer, ParsedColInfo> indexToColumnMap) { Integer ii = aggTableIndexMap.get(this); if (ii != null) { ParsedColInfo col = indexToColumnMap.get(ii); TupleValueExpression tve = new TupleValueExpression( col.tableName, col.tableAlias, col.columnName, col.alias, this, ii); if (this instanceof TupleValueExpression) { tve.setOrigStmtId(((TupleValueExpression)this).getOrigStmtId()); } // To prevent pushdown of LIMIT when ORDER BY references an agg. ENG-3487. if (hasAnySubexpressionOfClass(AggregateExpression.class)) { tve.setHasAggregate(true); } return tve; } AbstractExpression lnode = null; AbstractExpression rnode = null; if (m_left != null) { lnode = m_left.replaceWithTVE(aggTableIndexMap, indexToColumnMap); } if (m_right != null) { rnode = m_right.replaceWithTVE(aggTableIndexMap, indexToColumnMap); } ArrayList<AbstractExpression> newArgs = null; boolean changed = false; if (m_args != null) { newArgs = new ArrayList<>(); for (AbstractExpression expr: m_args) { AbstractExpression ex = expr.replaceWithTVE(aggTableIndexMap, indexToColumnMap); newArgs.add(ex); if (ex != expr) { changed = true; } } } if (m_left != lnode || m_right != rnode || changed) { AbstractExpression resExpr = clone(); resExpr.setLeft(lnode); resExpr.setRight(rnode); resExpr.setArgs(newArgs); return resExpr; } return this; } public boolean hasSubExpressionFrom(Set<AbstractExpression> expressionSet) { if (expressionSet.contains(this)) { return true; } if (m_left != null && expressionSet.contains(m_left)) { return true; } if (m_right != null && expressionSet.contains(m_right)) { return true; } if (m_args != null) { for (AbstractExpression expr: m_args) { if (expressionSet.contains(expr)) { return true; } } } return false; } /** * Replace avg expression with sum/count for optimization. * @return */ public AbstractExpression replaceAVG () { if (getExpressionType() == ExpressionType.AGGREGATE_AVG) { AbstractExpression child = getLeft(); AbstractExpression left = new AggregateExpression(ExpressionType.AGGREGATE_SUM); left.setLeft(child.clone()); AbstractExpression right = new AggregateExpression(ExpressionType.AGGREGATE_COUNT); right.setLeft(child.clone()); return new OperatorExpression(ExpressionType.OPERATOR_DIVIDE, left, right); } AbstractExpression lnode = null; AbstractExpression rnode = null; if (m_left != null) { lnode = m_left.replaceAVG(); } if (m_right != null) { rnode = m_right.replaceAVG(); } ArrayList<AbstractExpression> newArgs = null; boolean changed = false; if (m_args != null) { newArgs = new ArrayList<>(); for (AbstractExpression expr: m_args) { AbstractExpression ex = expr.replaceAVG(); newArgs.add(ex); if (ex != expr) { changed = true; } } } if (m_left != lnode || m_right != rnode || changed) { AbstractExpression resExpr = clone(); resExpr.setLeft(lnode); resExpr.setRight(rnode); resExpr.setArgs(newArgs); return resExpr; } return this; } /** * @param <aeClass> * @param aeClass AbstractExpression-based class of instances to search for. * @return a list of contained expressions that are instances of the desired class */ public <aeClass> List<aeClass> findAllSubexpressionsOfClass( Class< ? extends AbstractExpression> aeClass) { ArrayList<aeClass> collected = new ArrayList<>(); findAllSubexpressionsOfClass_recurse(aeClass, collected); return collected; } public <aeClass> void findAllSubexpressionsOfClass_recurse( Class< ? extends AbstractExpression> aeClass, ArrayList<aeClass> collected) { if (aeClass.isInstance(this)) { // Suppress the expected warning for the "unchecked" cast. // The runtime isInstance check ensures that it is typesafe. @SuppressWarnings("unchecked") aeClass e = (aeClass) this; collected.add(e); // Don't return early, because in a few rare cases, // like when searching for function expressions, // an instance CAN be a parent expression of another instance. // It's probably not worth optimizing for the special cases. } if (m_left != null) { m_left.findAllSubexpressionsOfClass_recurse(aeClass, collected); } if (m_right != null) { m_right.findAllSubexpressionsOfClass_recurse(aeClass, collected); } if (m_args != null) { for (AbstractExpression argument : m_args) { argument.findAllSubexpressionsOfClass_recurse(aeClass, collected); } } } /** * @param aeClass expression class to search for * @return whether the expression or any contained expressions are of the desired type */ public boolean hasAnySubexpressionOfClass( Class< ? extends AbstractExpression> aeClass) { if (aeClass.isInstance(this)) { return true; } if (m_left != null && m_left.hasAnySubexpressionOfClass(aeClass)) { return true; } if (m_right != null && m_right.hasAnySubexpressionOfClass(aeClass)) { return true; } if (m_args != null) { for (AbstractExpression argument : m_args) { if (argument.hasAnySubexpressionOfClass(aeClass)) { return true; } } } return false; } public boolean hasTVE() { return hasAnySubexpressionOfClass(TupleValueExpression.class); } /** * A predicate class for searching expression trees, * to be used with hasAnySubexpressionWithPredicate, below. */ public static interface SubexprFinderPredicate { boolean matches(AbstractExpression expr); } /** * Searches the expression tree rooted at this for nodes for which "pred" * evaluates to true. * @param pred Predicate object instantiated by caller * @return true if the predicate ever returns true, false otherwise */ public boolean hasAnySubexpressionWithPredicate(SubexprFinderPredicate pred) { if (pred.matches(this)) { return true; } if (m_left != null && m_left.hasAnySubexpressionWithPredicate(pred)) { return true; } if (m_right != null && m_right.hasAnySubexpressionWithPredicate(pred)) { return true; } if (m_args != null) { for (AbstractExpression argument : m_args) { if (argument.hasAnySubexpressionWithPredicate(pred)) { return true; } } } return false; } /** * 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 * OPERATOR_IS_NULL * AggregageExpression * * @return Does this expression need a right expression to be valid? */ public boolean needsRightExpression() { return false; } /** * Constant literals have a place-holder type of NUMERIC. These types * need to be converted to DECIMAL or FLOAT when used in a binary operator, * the choice based on the other operand's type * -- DECIMAL goes with DECIMAL, FLOAT goes with anything else. * This gets specialized as a NO-OP for leaf Expressions (AbstractValueExpression) */ public void normalizeOperandTypes_recurse() { // Depth first search for NUMERIC children. if (m_left != null) { m_left.normalizeOperandTypes_recurse(); } if (m_right != null) { m_right.normalizeOperandTypes_recurse(); // XXX: There's no check here that the Numeric operands are actually constants. // Can a sub-expression of type Numeric arise in any other case? // Would that case always be amenable to having its valueType/valueSize redefined here? if (m_left != null) { if (m_left.m_valueType == VoltType.NUMERIC) { m_left.refineOperandType(m_right.m_valueType); } if (m_right.m_valueType == VoltType.NUMERIC) { m_right.refineOperandType(m_left.m_valueType); } } } if (m_args != null) { for (AbstractExpression argument : m_args) { argument.normalizeOperandTypes_recurse(); } } } /** * Helper function to patch up NUMERIC typed constant operands and * the functions and operators that they parameterize. */ void refineOperandType(VoltType valueType) { if (m_valueType != VoltType.NUMERIC) { return; } if (valueType == VoltType.DECIMAL) { m_valueType = VoltType.DECIMAL; m_valueSize = VoltType.DECIMAL.getLengthInBytesForFixedTypes(); } else { m_valueType = VoltType.FLOAT; m_valueSize = VoltType.FLOAT.getLengthInBytesForFixedTypes(); } } /** * Specify or specialize the type and optionally the maximum size of an expression value. * This is used when the expression's type or size is implied or constrained by its usage, * especially to match the type/size of a target column in an INSERT or UPDATE statement. * This default implementation is currently a no-op, which covers expressions whose value * type/size is pre-determined, like logical expressions, comparisons, tuple address * expressions, etc. * Derived classes define custom implementations typically to allow refinement of types or * sizes for expressions that the HSQL parser was unable to determine the type of, leaving * the type as null, or was only able to narrow down to NUMERIC (of some undetermined type). * Type refinement can have an effect on the interpretation of constant values. * E.g. is this constant string being used as a timestamp? If so, a compile-time check for * a valid date/time format could provide a valuable heads-up to the user. * It also can bolster parameter type checking. * E.g. should values for this parameter only be allowed in the tiny int range? * @param neededType - the target type of the value as suggested by its usage. * @param neededSize - the maximum allowed size for the value as suggested by its usage. * TODO: we might want to enable asserts when we appear to be trying to refine a type or size * on an expression that expects to have a fixed pre-determined type -- at least when * non-trivially trying to refine the type/size to something other than its current type/size. * The ONLY reason this default no-op implementation contains any code at all is to make it * easier to catch such cases in a debugger, possibly in preparation for a more aggressive assert. */ public void refineValueType(VoltType neededType, int neededSize) { if (neededType.equals(m_valueType)) { return; // HSQL already initialized the expression to have the refined type. } //TODO: For added safety, we MAY want to (re)enable this general assert // OR only assert after we give a pass for refining from "generic types". See the comment below. // assert(false); if ((m_valueType != null) && (m_valueType != VoltType.NUMERIC)) { // This code path leaves a generic type (null or NUMERIC) in the expression tree rather // than assume that it's safe to change the value type of an arbitrary AbstractExpression. // The EE MAY complain about it later. // There may be special cases where we want to assert(false) rather than waiting for the EE // to complain or even special cases where we want to go ahead and switch the type (scary!). return; } // A request to switch an arbitrary AbstractExpression from the specific value type that HSQL // assigned to it to a different specific value type that SEEMS to be called for by the usage // (target column) is a hard thing to know how to handle. // It seems equally dangerous to ignore the HSQL type OR the target type. // Maybe this will never occur because HSQL is so smart (and we keep it that way), // or maybe the type difference won't really matter to the EE because it's so flexible. // Or maybe some of each -- this no-op behavior assumes something like that. // BUT maybe it's just always wrong to be trying any such thing, so we should assert(false). // Or maybe there need to be special cases. // The sad news is that when this assert first went live, it inexplicably caused quiet // thread death and hanging rather than an identifiable assert, even when run in the debugger. // assert(false); } /** Instead of recursing by default, allow derived classes to recurse as needed * using finalizeChildValueTypes. */ public abstract void finalizeValueTypes(); /** * Do the recursive part of finalizeValueTypes as requested. Note that this * updates the content non-determinism state. */ protected final void finalizeChildValueTypes() { if (m_left != null) { m_left.finalizeValueTypes(); updateContentDeterminismMessage(m_left.getContentDeterminismMessage()); } if (m_right != null) { m_right.finalizeValueTypes(); updateContentDeterminismMessage(m_right.getContentDeterminismMessage()); } if (m_args != null) { for (AbstractExpression argument : m_args) { argument.finalizeValueTypes(); updateContentDeterminismMessage(argument.getContentDeterminismMessage()); } } } /** * Associate underlying TupleValueExpressions with columns in the table * and propagate the type implications to parent expressions. */ public void resolveForTable(Table table) { resolveChildrenForTable(table); } /** * Do the recursive part of resolveForTable * as required for tree-structured expression types. */ protected final void resolveChildrenForTable(Table table) { if (m_left != null) { m_left.resolveForTable(table); } if (m_right != null) { m_right.resolveForTable(table); } if (m_args != null) { for (AbstractExpression argument : m_args) { argument.resolveForTable(table); } } } public abstract String explain(String impliedTableName); public static boolean hasInlineVarType(AbstractExpression expr) { VoltType type = expr.getValueType(); int size = expr.getValueSize(); boolean inBytes = expr.getInBytes(); switch(type) { case STRING: if (inBytes && size < 64) { return true; } if (!inBytes && size < 16) { return true; } break; case VARBINARY: if (size < 64) { return true; } break; default: break; } return false; } /** * Return true if the given expression usable as part of an index or MV's * group by and where clause expression. * If false, put the tail of an error message in the string buffer. The * string buffer will be initialized with the name of the index. * * @param expr The expression to check * @param msg The StringBuffer to pack with the error message tail. * @return true iff the expression can be part of an index. */ private boolean validateExprForIndexesAndMVs(StringBuffer msg) { if (containsFunctionById(FunctionSQL.voltGetCurrentTimestampId())) { msg.append("cannot include the function NOW or CURRENT_TIMESTAMP."); return false; } if (hasSubquerySubexpression()) { // There may not be any of these in HSQL1.9.3b. However, in // HSQL2.3.2 subqueries are stored as expressions. So, we may // find some here. We will keep it here for the moment. msg.append(String.format("with subquery sources is not supported.")); return false; } if (hasAggregateSubexpression()) { msg.append("with aggregate expression(s) is not supported."); return false; } return true; } public boolean hasSubquerySubexpression() { return !findAllSubexpressionsOfClass(SelectSubqueryExpression.class).isEmpty(); } public boolean hasAggregateSubexpression() { return !findAllSubexpressionsOfClass(AggregateExpression.class).isEmpty(); } public boolean hasParameterSubexpression() { return !findAllSubexpressionsOfClass(ParameterValueExpression.class).isEmpty(); } public boolean hasTupleValueSubexpression() { return !findAllSubexpressionsOfClass(TupleValueExpression.class).isEmpty(); } public List<AbstractExpression> findAllSubquerySubexpressions() { return findAllSubexpressionsOfClass(SelectSubqueryExpression.class); } public List<AbstractExpression> findAllAggregateSubexpressions() { return findAllSubexpressionsOfClass(AggregateExpression.class); } public List<AbstractExpression> findAllParameterSubexpressions() { return findAllSubexpressionsOfClass(ParameterValueExpression.class); } public List<TupleValueExpression> findAllTupleValueSubexpressions() { return findAllSubexpressionsOfClass(TupleValueExpression.class); } /** * Return true if the all of the expressions in the list can be part of * an index expression or in group by and where clause of MV. As with * validateExprForIndexesAndMVs for individual expression, the StringBuffer * parameter, msg, contains the name of the index. Error messages should * be appended to it. * * @param checkList * @param msg * @return */ public static boolean validateExprsForIndexesAndMVs(List<AbstractExpression> checkList, StringBuffer msg) { for (AbstractExpression expr : checkList) { if (!expr.validateExprForIndexesAndMVs(msg)) { return false; } } return true; } /** * This function will recursively find any function expression with ID functionId. * If found, return true. Otherwise, return false. * * @param expr * @param functionId * @return */ private boolean containsFunctionById(int functionId) { if (this instanceof AbstractValueExpression) { return false; } List<AbstractExpression> functionsList = findAllFunctionSubexpressions(); for (AbstractExpression funcExpr: functionsList) { assert(funcExpr instanceof FunctionExpression); if (((FunctionExpression)funcExpr).hasFunctionId(functionId)) { return true; } } return false; } private List<AbstractExpression> findAllFunctionSubexpressions() { return findAllSubexpressionsOfClass(FunctionExpression.class); } /** * Returns true iff the expression is indexable. * If the expression is not indexable, expression information * gets populated in the msg string buffer passed in. * @param msg * @return */ public boolean isValueTypeIndexable(StringBuffer msg) { if (!m_valueType.isIndexable()) { msg.append("expression of type " + m_valueType.getName()); return false; } return true; } /** * Returns true iff the expression is indexable in a unique index. * If the expression is not indexable, expression information * gets populated in the msg string buffer passed in. * @param msg * @return */ public boolean isValueTypeUniqueIndexable(StringBuffer msg) { // This call to isValueTypeIndexable is needed because // all comparison, all conjunction, and some operator expressions // need to refine it to compensate for their false claims that // their value types (actually non-indexable boolean) is BIGINT. // that their value type is actually boolean. // If they were fixed, isValueTypeIndexable and // isValueTypeUniqueIndexable could be replaced by VoltType functions. if (!isValueTypeIndexable(msg)) { return false; } if (!m_valueType.isUniqueIndexable()) { msg.append("expression of type " + m_valueType.getName()); return false; } return true; } /** * Little objects of this class keep track of operators * which can make an expression unsafe for use in creating * materialized views on non-empty tables. */ public static class UnsafeOperatorsForDDL { public final void add(String opName) { m_oplist.append(m_sep) .append(opName); m_sep = ", "; m_isUnsafe = true; } @Override public String toString() { return m_oplist.toString(); } public final boolean isUnsafe() { return m_isUnsafe; } private String m_sep = ""; private final StringBuffer m_oplist = new StringBuffer(); private boolean m_isUnsafe = false; }; /** * Returns true iff this expression is allowable when creating * materialized views on nonempty tables. We have marked all * the ExpressionType enumerals and all the function id integers * which are safe. These are marked statically. So we just * recurse through the tree, looking at operation types and * function types until we find something we don't like. If * we get all the way through the search we are happy, and * return true. */ public void findUnsafeOperatorsForDDL(UnsafeOperatorsForDDL ops) { if ( ! m_type.isSafeForDDL()) { ops.add(m_type.symbol()); } if (m_left != null) { m_left.findUnsafeOperatorsForDDL(ops); } if (m_right != null) { m_right.findUnsafeOperatorsForDDL(ops); } if (m_args != null) { for (AbstractExpression arg : m_args) { arg.findUnsafeOperatorsForDDL(ops); } } } public static void toJSONArray(JSONStringer stringer, String keyString, List<AbstractExpression> exprs) throws JSONException { stringer.key(keyString) .array(); if (exprs != null) { for (AbstractExpression ae : exprs) { stringer.object(); ae.toJSONString(stringer); stringer.endObject(); } } stringer.endArray(); } /** * Ferret out the first argument. This can be m_left or else * the first element of m_args. */ public AbstractExpression getFirstArgument() { if (m_left != null) { assert(m_args == null); return m_left; } if (m_args != null && m_args.size() > 0) { assert(m_left == null && m_right == null); return m_args.get(0); } return null; } public boolean isColumnEquivalenceFilter() { // Ignore expressions that are not of COMPARE_EQUAL or // COMPARE_NOTDISTINCT type ExpressionType type = getExpressionType(); if (type != ExpressionType.COMPARE_EQUAL && type != ExpressionType.COMPARE_NOTDISTINCT) { return false; } AbstractExpression leftExpr = getLeft(); // 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; } AbstractExpression rightExpr = getRight(); if ( ( ! (rightExpr instanceof TupleValueExpression)) && rightExpr.hasAnySubexpressionOfClass(TupleValueExpression.class) ) { return false; } return true; } }