/* Copyright (c) 1995-2000, The Hypersonic SQL Group.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the Hypersonic SQL Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* on behalf of the Hypersonic SQL Group.
*
*
* For work added by the HSQL Development Group:
*
* Copyright (c) 2001-2009, The HSQL Development Group
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the HSQL Development Group nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.hsqldb_voltpatches;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import org.hsqldb_voltpatches.HSQLInterface.HSQLParseException;
import org.hsqldb_voltpatches.HsqlNameManager.SimpleName;
import org.hsqldb_voltpatches.ParserDQL.CompileContext;
import org.hsqldb_voltpatches.lib.ArrayListIdentity;
import org.hsqldb_voltpatches.lib.HsqlArrayList;
import org.hsqldb_voltpatches.lib.HsqlList;
import org.hsqldb_voltpatches.lib.OrderedHashSet;
import org.hsqldb_voltpatches.lib.OrderedIntHashSet;
import org.hsqldb_voltpatches.lib.Set;
import org.hsqldb_voltpatches.persist.PersistentStore;
import org.hsqldb_voltpatches.types.BinaryData;
import org.hsqldb_voltpatches.types.CharacterType;
import org.hsqldb_voltpatches.types.NullType;
import org.hsqldb_voltpatches.types.NumberType;
import org.hsqldb_voltpatches.types.TimestampData;
import org.hsqldb_voltpatches.types.Type;
/**
* Expression class.
*
* @author Campbell Boucher-Burnett (boucherb@users dot sourceforge.net)
* @author Fred Toussi (fredt@users dot sourceforge.net)
* @version 1.9.0
* @since 1.9.0
*/
public class Expression {
public static final int LEFT = 0;
public static final int RIGHT = 1;
public static final int UNARY = 1;
public static final int BINARY = 2;
//
//
static final Expression[] emptyExpressionArray = new Expression[]{};
//
static final Expression EXPR_TRUE = new ExpressionLogical(true);
static final Expression EXPR_FALSE = new ExpressionLogical(false);
//
static final OrderedIntHashSet aggregateFunctionSet =
new OrderedIntHashSet();
static {
aggregateFunctionSet.add(OpTypes.COUNT);
// A VoltDB extension APPROX_COUNT_DISTINCT
aggregateFunctionSet.add(OpTypes.APPROX_COUNT_DISTINCT);
// End of VoltDB extension
aggregateFunctionSet.add(OpTypes.SUM);
aggregateFunctionSet.add(OpTypes.MIN);
aggregateFunctionSet.add(OpTypes.MAX);
aggregateFunctionSet.add(OpTypes.AVG);
aggregateFunctionSet.add(OpTypes.EVERY);
aggregateFunctionSet.add(OpTypes.SOME);
aggregateFunctionSet.add(OpTypes.STDDEV_POP);
aggregateFunctionSet.add(OpTypes.STDDEV_SAMP);
aggregateFunctionSet.add(OpTypes.VAR_POP);
aggregateFunctionSet.add(OpTypes.VAR_SAMP);
}
static final OrderedIntHashSet columnExpressionSet =
new OrderedIntHashSet();
static {
columnExpressionSet.add(OpTypes.COLUMN);
}
static final OrderedIntHashSet subqueryExpressionSet =
new OrderedIntHashSet();
static {
subqueryExpressionSet.add(OpTypes.ROW_SUBQUERY);
subqueryExpressionSet.add(OpTypes.TABLE_SUBQUERY);
}
static final OrderedIntHashSet subqueryAggregateExpressionSet =
new OrderedIntHashSet();
static {
subqueryAggregateExpressionSet.add(OpTypes.COUNT);
// A VoltDB extension APPROX_COUNT_DISTINCT
subqueryAggregateExpressionSet.add(OpTypes.APPROX_COUNT_DISTINCT);
// End of VoltDB extension
subqueryAggregateExpressionSet.add(OpTypes.SUM);
subqueryAggregateExpressionSet.add(OpTypes.MIN);
subqueryAggregateExpressionSet.add(OpTypes.MAX);
subqueryAggregateExpressionSet.add(OpTypes.AVG);
subqueryAggregateExpressionSet.add(OpTypes.EVERY);
subqueryAggregateExpressionSet.add(OpTypes.SOME);
subqueryAggregateExpressionSet.add(OpTypes.STDDEV_POP);
subqueryAggregateExpressionSet.add(OpTypes.STDDEV_SAMP);
subqueryAggregateExpressionSet.add(OpTypes.VAR_POP);
subqueryAggregateExpressionSet.add(OpTypes.VAR_SAMP);
//
subqueryAggregateExpressionSet.add(OpTypes.TABLE_SUBQUERY);
subqueryAggregateExpressionSet.add(OpTypes.ROW_SUBQUERY);
}
static final OrderedIntHashSet emptyExpressionSet =
new OrderedIntHashSet();
// type
protected int opType;
// type qualifier
protected int exprSubType;
//
SimpleName alias;
// aggregate
protected boolean isAggregate;
// VALUE
protected Object valueData;
protected Expression[] nodes;
Type[] nodeDataTypes;
// QUERY - in single value selects, IN, EXISTS etc.
SubQuery subQuery;
// for query and value lists, etc
boolean isCorrelated;
// for COLUMN
int columnIndex = -1;
// data type
protected Type dataType;
//
int queryTableColumnIndex = -1; // >= 0 when it is used for order by
boolean isParam;
// index of a session-dependent field
int parameterIndex = -1;
//
int rangePosition = -1;
//
boolean isColumnEqual;
Expression(int type) {
opType = type;
nodes = emptyExpressionArray;
}
// IN condition optimisation
/**
* Creates a SCALAR SUBQUERY expression. Is called as ROW_SUBQUERY
*/
Expression(int exprType, SubQuery sq) {
this(OpTypes.TABLE_SUBQUERY);
subQuery = sq;
}
/**
* ROW or VALUELIST
*/
Expression(int type, Expression[] list) {
this(type);
nodes = list;
}
public String describe(Session session) {
return describe(session, 0);
}
static String getContextSQL(Expression expression) {
if (expression == null) {
return null;
}
String ddl = expression.getSQL();
switch (expression.opType) {
case OpTypes.VALUE :
case OpTypes.COLUMN :
case OpTypes.ROW :
case OpTypes.FUNCTION :
case OpTypes.SQL_FUNCTION :
case OpTypes.ALTERNATIVE :
case OpTypes.CASEWHEN :
case OpTypes.CAST :
return ddl;
}
StringBuffer sb = new StringBuffer();
ddl = sb.append('(').append(ddl).append(')').toString();
return ddl;
}
/**
* For use with CHECK constraints. Under development.
*
* Currently supports a subset of expressions and is suitable for CHECK
* search conditions that refer only to the inserted/updated row.
*
* For full DDL reporting of VIEW select queries and CHECK search
* conditions, future improvements here are dependent upon improvements to
* SELECT query parsing, so that it is performed in a number of passes.
* An early pass should result in the query turned into an Expression tree
* that contains the information in the original SQL without any
* alterations, and with tables and columns all resolved. This Expression
* can then be preserved for future use. Table and column names that
* are not user-defined aliases should be kept as the HsqlName structures
* so that table or column renaming is reflected in the precompiled
* query.
*/
public String getSQL() {
StringBuffer sb = new StringBuffer(64);
switch (opType) {
case OpTypes.VALUE :
if (valueData == null) {
return Tokens.T_NULL;
}
return dataType.convertToSQLString(valueData);
case OpTypes.ROW :
sb.append('(');
for (int i = 0; i < nodes.length; i++) {
sb.append(nodes[i].getSQL());
if (i < nodes.length - 1) {
sb.append(',');
}
}
sb.append(')');
return sb.toString();
//
case OpTypes.TABLE :
for (int i = 0; i < nodes.length; i++) {
sb.append(nodes[i].getSQL());
if (i < nodes.length - 1) {
sb.append(',');
}
}
return sb.toString();
}
switch (opType) {
case OpTypes.ROW_SUBQUERY :
case OpTypes.TABLE_SUBQUERY :
/*
buf.append('(');
buf.append(subSelect.getSQL());
buf.append(')');
*/
break;
default :
throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
}
return sb.toString();
}
protected String describe(Session session, int blanks) {
StringBuffer sb = new StringBuffer(64);
sb.append('\n');
for (int i = 0; i < blanks; i++) {
sb.append(' ');
}
switch (opType) {
case OpTypes.VALUE :
sb.append("VALUE = ").append(valueData);
sb.append(", TYPE = ").append(dataType.getNameString());
break;
case OpTypes.ROW_SUBQUERY :
case OpTypes.TABLE_SUBQUERY :
sb.append("QUERY ");
sb.append(subQuery.queryExpression.describe(session));
break;
case OpTypes.ROW :
sb.append("ROW = (");
for (Expression node : nodes) {
sb.append(node.describe(session, blanks + 1));
}
sb.append("), TYPE = ")
.append(dataType == null ? "null" : dataType.getNameString());
break;
case OpTypes.TABLE :
sb.append("VALUELIST ");
for (Expression node : nodes) {
sb.append(node.describe(session, blanks + 1));
sb.append(' ');
}
break;
default:
if (nodes.length > LEFT && nodes[LEFT] != null) {
sb.append(" arg1=[");
sb.append(nodes[LEFT].describe(session, blanks + 1));
sb.append(']');
}
if (nodes.length > RIGHT && nodes[RIGHT] != null) {
sb.append(" arg2=[");
sb.append(nodes[RIGHT].describe(session, blanks + 1));
sb.append(']');
}
}
return sb.toString();
}
/**
* Set the data type
*/
void setDataType(Session session, Type type) {
if (opType == OpTypes.VALUE) {
valueData = type.convertToType(session, valueData, dataType);
}
dataType = type;
}
public boolean equals(Expression other) {
if (other == this) {
return true;
}
if (other == null) {
return false;
}
if (opType != other.opType || exprSubType != other.exprSubType
|| !equals(dataType, other.dataType)) {
return false;
}
switch (opType) {
case OpTypes.SIMPLE_COLUMN :
return columnIndex == other.columnIndex;
case OpTypes.VALUE :
return equals(valueData, other.valueData);
default :
return equals(nodes, other.nodes)
&& equals(subQuery, other.subQuery);
}
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (other instanceof Expression) {
return equals((Expression) other);
}
return false;
}
@Override
public int hashCode() {
int val = opType + exprSubType;
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] != null) {
val += nodes[i].hashCode();
}
}
return val;
}
static boolean equals(Object o1, Object o2) {
if (o1 == o2) {
return true;
}
return (o1 == null) ? o2 == null
: o1.equals(o2);
}
static boolean equals(Expression[] row1, Expression[] row2) {
if (row1 == row2) {
return true;
}
if (row1.length != row2.length) {
return false;
}
int len = row1.length;
for (int i = 0; i < len; i++) {
Expression e1 = row1[i];
Expression e2 = row2[i];
boolean equals = (e1 == null) ? e2 == null
: e1.equals(e2);
if (!equals) {
return false;
}
}
return true;
}
boolean isComposedOf(Expression exprList[], int start, int end,
OrderedIntHashSet excludeSet) {
if (opType == OpTypes.VALUE) {
return true;
}
if (excludeSet.contains(opType)) {
return true;
}
for (int i = start; i < end; i++) {
if (equals(exprList[i])) {
return true;
}
}
switch (opType) {
case OpTypes.LIKE :
case OpTypes.MATCH_SIMPLE :
case OpTypes.MATCH_PARTIAL :
case OpTypes.MATCH_FULL :
case OpTypes.MATCH_UNIQUE_SIMPLE :
case OpTypes.MATCH_UNIQUE_PARTIAL :
case OpTypes.MATCH_UNIQUE_FULL :
case OpTypes.UNIQUE :
case OpTypes.EXISTS :
case OpTypes.TABLE_SUBQUERY :
case OpTypes.ROW_SUBQUERY :
//
case OpTypes.COUNT :
// A VoltDB extension APPROX_COUNT_DISTINCT
case OpTypes.APPROX_COUNT_DISTINCT:
// End of VoltDB extension
case OpTypes.SUM :
case OpTypes.MIN :
case OpTypes.MAX :
case OpTypes.AVG :
case OpTypes.EVERY :
case OpTypes.SOME :
case OpTypes.STDDEV_POP :
case OpTypes.STDDEV_SAMP :
case OpTypes.VAR_POP :
case OpTypes.VAR_SAMP :
return false;
}
if (nodes.length == 0) {
return false;
}
boolean result = true;
for (int i = 0; i < nodes.length; i++) {
result &= (nodes[i] == null
|| nodes[i].isComposedOf(exprList, start, end,
excludeSet));
}
return result;
}
boolean isComposedOf(OrderedHashSet expressions,
OrderedIntHashSet excludeSet) {
// BEGIN Cherry-picked code change from hsqldb-2.3.2
if (opType == OpTypes.VALUE || opType == OpTypes.DYNAMIC_PARAM
|| opType == OpTypes.PARAMETER || opType == OpTypes.VARIABLE) {
return true;
}
// END Cherry-picked code change from hsqldb-2.3.2
if (excludeSet.contains(opType)) {
return true;
}
for (int i = 0; i < expressions.size(); i++) {
if (equals(expressions.get(i))) {
return true;
}
}
switch (opType) {
case OpTypes.COUNT :
// A VoltDB extension APPROX_COUNT_DISTINCT
case OpTypes.APPROX_COUNT_DISTINCT:
// End of VoltDB extension
case OpTypes.SUM :
case OpTypes.MIN :
case OpTypes.MAX :
case OpTypes.AVG :
case OpTypes.EVERY :
case OpTypes.SOME :
case OpTypes.STDDEV_POP :
case OpTypes.STDDEV_SAMP :
case OpTypes.VAR_POP :
case OpTypes.VAR_SAMP :
return false;
}
/*
case OpCodes.LIKE :
case OpCodes.ALL :
case OpCodes.ANY :
case OpCodes.IN :
case OpCodes.MATCH_SIMPLE :
case OpCodes.MATCH_PARTIAL :
case OpCodes.MATCH_FULL :
case OpCodes.MATCH_UNIQUE_SIMPLE :
case OpCodes.MATCH_UNIQUE_PARTIAL :
case OpCodes.MATCH_UNIQUE_FULL :
case OpCodes.UNIQUE :
case OpCodes.EXISTS :
case OpCodes.TABLE_SUBQUERY :
case OpCodes.ROW_SUBQUERY :
*/
if (nodes.length == 0) {
return false;
}
boolean result = true;
for (int i = 0; i < nodes.length; i++) {
result &= (nodes[i] == null
|| nodes[i].isComposedOf(expressions, excludeSet));
}
return result;
}
Expression replace(OrderedHashSet expressions,
OrderedHashSet replacements) {
if (opType == OpTypes.VALUE) {
return this;
}
int index = expressions.getIndex(this);
if (index != -1) {
return (Expression) replacements.get(index);
}
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] == null) {
continue;
}
nodes[i] = nodes[i].replace(expressions, replacements);
}
return this;
}
Expression replaceColumnReferences(RangeVariable range,
Expression[] list) {
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] == null) {
continue;
}
nodes[i] = nodes[i].replaceColumnReferences(range, list);
}
return this;
}
void convertToSimpleColumn(OrderedHashSet expressions,
OrderedHashSet replacements) {
if (opType == OpTypes.VALUE) {
return;
}
int index = expressions.getIndex(this);
if (index != -1) {
Expression e = (Expression) replacements.get(index);
nodes = emptyExpressionArray;
opType = OpTypes.SIMPLE_COLUMN;
columnIndex = e.columnIndex;
rangePosition = e.rangePosition;
return;
}
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] == null) {
continue;
}
nodes[i].convertToSimpleColumn(expressions, replacements);
}
}
boolean isSelfAggregate() {
return false;
}
/**
* Set the column alias
*/
void setAlias(SimpleName name) {
alias = name;
}
/**
* Get the column alias
*/
String getAlias() {
if (alias != null) {
return alias.name;
}
return "";
}
SimpleName getSimpleName() {
if (alias != null) {
return alias;
}
return null;
}
/**
* Returns the type of expression
*/
public int getType() {
return opType;
}
/**
* Returns the left node
*/
Expression getLeftNode() {
return nodes.length > 0 ? nodes[LEFT]
: null;
}
/**
* Returns the right node
*/
Expression getRightNode() {
return nodes.length > 1 ? nodes[RIGHT]
: null;
}
void setLeftNode(Expression e) {
nodes[LEFT] = e;
}
void setRightNode(Expression e) {
nodes[RIGHT] = e;
}
/**
* Returns the range variable for a COLUMN expression
*/
RangeVariable getRangeVariable() {
return null;
}
/**
* return the expression for an alias used in an ORDER BY clause
*/
Expression replaceAliasInOrderBy(Expression[] columns, int length) {
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] == null) {
continue;
}
nodes[i] = nodes[i].replaceAliasInOrderBy(columns, length);
}
return this;
}
/**
* Find a range variable with the given table alias
*/
int findMatchingRangeVariableIndex(RangeVariable[] rangeVarArray) {
return -1;
}
/**
* collects all range variables in expression tree
*/
void collectRangeVariables(RangeVariable[] rangeVariables, Set set) {
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] != null) {
nodes[i].collectRangeVariables(rangeVariables, set);
}
}
if (subQuery != null && subQuery.queryExpression != null) {
HsqlList unresolvedExpressions =
subQuery.queryExpression.getUnresolvedExpressions();
if (unresolvedExpressions != null) {
for (int i = 0; i < unresolvedExpressions.size(); i++) {
Expression e = (Expression) unresolvedExpressions.get(i);
e.collectRangeVariables(rangeVariables, set);
}
}
}
}
/**
* collects all range variables in expression tree
*/
void collectObjectNames(Set set) {
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] != null) {
nodes[i].collectObjectNames(set);
}
}
if (subQuery != null) {
if (subQuery.queryExpression != null) {
subQuery.queryExpression.collectObjectNames(set);
}
}
}
/**
* return true if given RangeVariable is used in expression tree
*/
boolean hasReference(RangeVariable range) {
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] != null) {
if (nodes[i].hasReference(range)) {
return true;
}
}
}
if (subQuery != null && subQuery.queryExpression != null) {
if (subQuery.queryExpression.hasReference(range)) {
return true;
}
}
return false;
}
/**
* resolve tables and collect unresolved column expressions
*/
public HsqlList resolveColumnReferences(RangeVariable[] rangeVarArray,
HsqlList unresolvedSet) {
return resolveColumnReferences(rangeVarArray, rangeVarArray.length,
unresolvedSet, true);
}
public HsqlList resolveColumnReferences(RangeVariable[] rangeVarArray,
int rangeCount, HsqlList unresolvedSet, boolean acceptsSequences) {
if (opType == OpTypes.VALUE) {
return unresolvedSet;
}
switch (opType) {
case OpTypes.CASEWHEN :
acceptsSequences = false;
break;
case OpTypes.TABLE : {
HsqlList localSet = null;
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] == null) {
continue;
}
localSet = nodes[i].resolveColumnReferences(
RangeVariable.emptyArray, localSet);
}
if (localSet != null) {
isCorrelated = true;
if (subQuery != null) {
subQuery.setCorrelated();
}
for (int i = 0; i < localSet.size(); i++) {
Expression e = (Expression) localSet.get(i);
unresolvedSet =
e.resolveColumnReferences(rangeVarArray,
unresolvedSet);
}
unresolvedSet = Expression.resolveColumnSet(rangeVarArray,
localSet, unresolvedSet);
}
return unresolvedSet;
}
}
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] == null) {
continue;
}
unresolvedSet = nodes[i].resolveColumnReferences(rangeVarArray,
rangeCount, unresolvedSet, acceptsSequences);
}
switch (opType) {
case OpTypes.ROW_SUBQUERY :
case OpTypes.TABLE_SUBQUERY : {
QueryExpression queryExpression = subQuery.queryExpression;
if (!queryExpression.areColumnsResolved()) {
isCorrelated = true;
subQuery.setCorrelated();
// take to enclosing context
if (unresolvedSet == null) {
unresolvedSet = new ArrayListIdentity();
}
unresolvedSet.addAll(
queryExpression.getUnresolvedExpressions());
}
break;
}
default :
}
return unresolvedSet;
}
public OrderedHashSet getUnkeyedColumns(OrderedHashSet unresolvedSet) {
if (opType == OpTypes.VALUE) {
return unresolvedSet;
}
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] == null) {
continue;
}
unresolvedSet = nodes[i].getUnkeyedColumns(unresolvedSet);
}
switch (opType) {
case OpTypes.ROW_SUBQUERY :
case OpTypes.TABLE_SUBQUERY :
if (subQuery != null) {
if (unresolvedSet == null) {
unresolvedSet = new OrderedHashSet();
}
unresolvedSet.add(this);
}
break;
}
return unresolvedSet;
}
public void resolveTypes(Session session, Expression parent) {
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] != null) {
nodes[i].resolveTypes(session, this);
}
}
switch (opType) {
case OpTypes.VALUE :
break;
case OpTypes.TABLE :
/** @todo - should it fall through */
break;
case OpTypes.ROW :
nodeDataTypes = new Type[nodes.length];
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] != null) {
nodeDataTypes[i] = nodes[i].dataType;
}
}
break;
case OpTypes.ROW_SUBQUERY :
case OpTypes.TABLE_SUBQUERY : {
QueryExpression queryExpression = subQuery.queryExpression;
queryExpression.resolveTypes(session);
subQuery.prepareTable(session);
nodeDataTypes = queryExpression.getColumnTypes();
dataType = nodeDataTypes[0];
break;
}
default :
throw Error.runtimeError(ErrorCode.U_S0500,
"Expression.resolveTypes()");
}
}
void setAsConstantValue(Session session) {
valueData = getConstantValue(session);
opType = OpTypes.VALUE;
nodes = emptyExpressionArray;
}
void setAsConstantValue(Object value) {
valueData = value;
opType = OpTypes.VALUE;
nodes = emptyExpressionArray;
}
void prepareTable(Session session, Expression row, int degree) {
if (nodeDataTypes != null) {
return;
}
for (int i = 0; i < nodes.length; i++) {
Expression e = nodes[i];
if (e.opType == OpTypes.ROW) {
if (degree != e.nodes.length) {
throw Error.error(ErrorCode.X_42564);
}
} else if (degree == 1) {
nodes[i] = new Expression(OpTypes.ROW);
nodes[i].nodes = new Expression[]{ e };
} else {
throw Error.error(ErrorCode.X_42564);
}
}
nodeDataTypes = new Type[degree];
for (int j = 0; j < degree; j++) {
Type type = row == null ? null
: row.nodes[j].dataType;
for (int i = 0; i < nodes.length; i++) {
type = Type.getAggregateType(nodes[i].nodes[j].dataType, type);
}
if (type == null) {
throw Error.error(ErrorCode.X_42567);
}
nodeDataTypes[j] = type;
if (row != null && row.nodes[j].isParam) {
row.nodes[j].dataType = type;
}
for (int i = 0; i < nodes.length; i++) {
if (nodes[i].nodes[j].isParam) {
nodes[i].nodes[j].dataType = nodeDataTypes[j];
continue;
}
if (nodes[i].nodes[j].opType == OpTypes.VALUE) {
if (nodes[i].nodes[j].valueData == null) {
nodes[i].nodes[j].dataType = nodeDataTypes[j];
}
}
}
if (nodeDataTypes[j].isCharacterType()
&& !((CharacterType) nodeDataTypes[j])
.isEqualIdentical()) {
// collation issues
}
}
}
/**
* Details of IN condition optimisation for 1.9.0
* Predicates with SELECT are QUERY expressions
*
* Predicates with IN list
*
* Parser adds a SubQuery to the list for each predicate
* At type resolution IN lists that are entirely fixed constant or parameter
* values are selected for possible optimisation. The flags:
*
* IN expression right side isCorrelated == true if there are non-constant,
* non-param expressions in the list (Expressions may have to be resolved
* against the full set of columns of the query, so must be re-evaluated
* for each row and evaluated after all the joins have been made)
*
* VALUELIST expression isFixedConstantValueList == true when all
* expressions are fixed constant and none is a param. With this flag,
* a single-column VALUELIST can be accessed as a HashMap.
*
* Predicates may be optimised as joins if isCorrelated == false
*
*/
void insertValuesIntoSubqueryTable(Session session,
PersistentStore store) {
for (int i = 0; i < nodes.length; i++) {
Object[] data = nodes[i].getRowValue(session);
for (int j = 0; j < nodeDataTypes.length; j++) {
data[j] = nodeDataTypes[j].convertToType(session, data[j],
nodes[i].nodes[j].dataType);
}
Row row = (Row) store.getNewCachedObject(session, data);
try {
store.indexRow(session, row);
}
//XXX: what conditions are being casually ignored here?
catch (HsqlException e) {}
}
}
/**
* Returns the name of a column as string
*
* @return column name
*/
String getColumnName() {
return getAlias();
}
ColumnSchema getColumn() {
return null;
}
/**
* Returns the column index in the table
*/
int getColumnIndex() {
return columnIndex;
}
/**
* Returns the data type
*/
Type getDataType() {
return dataType;
}
int getDegree() {
return opType == OpTypes.ROW ? nodes.length
: 1;
}
public Object[] getRowValue(Session session) {
switch (opType) {
case OpTypes.ROW : {
Object[] data = new Object[nodes.length];
for (int i = 0; i < nodes.length; i++) {
data[i] = nodes[i].getValue(session);
}
return data;
}
case OpTypes.ROW_SUBQUERY :
case OpTypes.TABLE_SUBQUERY : {
return subQuery.queryExpression.getValues(session);
}
default :
throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
}
}
Object getValue(Session session, Type type) {
Object o = getValue(session);
if (o == null || dataType == type) {
return o;
}
return type.convertToType(session, o, dataType);
}
public Object getConstantValue(Session session) {
return getValue(session);
}
public Object getConstantValueNoCheck(Session session) {
try {
return getValue(session);
}
catch (HsqlException e) {
return null;
}
}
public Object getValue(Session session) {
switch (opType) {
case OpTypes.VALUE :
return valueData;
case OpTypes.SIMPLE_COLUMN : {
Object[] data =
session.sessionContext.rangeIterators[rangePosition]
.getCurrent();
return data[columnIndex];
}
case OpTypes.ROW : {
if (nodes.length == 1) {
return nodes[0].getValue(session);
}
Object[] row = new Object[nodes.length];
for (int i = 0; i < nodes.length; i++) {
row[i] = nodes[i].getValue(session);
}
return row;
}
case OpTypes.ROW_SUBQUERY :
case OpTypes.TABLE_SUBQUERY : {
subQuery.materialiseCorrelated(session);
Object value = subQuery.getValue(session);
return value;
}
default :
throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
}
}
boolean testCondition(Session session) {
return Boolean.TRUE.equals(getValue(session));
}
static int countNulls(Object[] a) {
int nulls = 0;
for (int i = 0; i < a.length; i++) {
if (a[i] == null) {
nulls++;
}
}
return nulls;
}
static void convertToType(Session session, Object[] data, Type[] dataType,
Type[] newType) {
for (int i = 0; i < data.length; i++) {
data[i] = newType[i].convertToType(session, data[i], dataType[i]);
}
}
/**
* Returns a Select object that can be used for checking the contents
* of an existing table against the given CHECK search condition.
*/
static QuerySpecification getCheckSelect(Session session, Table t,
Expression e) {
CompileContext compileContext = new CompileContext(session);
QuerySpecification s = new QuerySpecification(compileContext);
s.exprColumns = new Expression[1];
s.exprColumns[0] = EXPR_TRUE;
RangeVariable range = new RangeVariable(t, null, null, null,
compileContext);
s.rangeVariables = new RangeVariable[]{ range };
HsqlList unresolved = e.resolveColumnReferences(s.rangeVariables,
null);
ExpressionColumn.checkColumnsResolved(unresolved);
e.resolveTypes(session, null);
if (Type.SQL_BOOLEAN != e.getDataType()) {
throw Error.error(ErrorCode.X_42568);
}
Expression condition = new ExpressionLogical(OpTypes.NOT, e);
s.queryCondition = condition;
s.resolveReferences(session);
s.resolveTypes(session);
return s;
}
boolean isParam() {
return isParam;
}
void setAttributesAsColumn(ColumnSchema column, boolean isWritable) {
throw Error.runtimeError(ErrorCode.U_S0500,
"Expression.setAttributesAsColumn");
}
String getValueClassName() {
Type type = dataType == null ? NullType.getNullType()
: dataType;
return type.getJDBCClassName();
}
public void collectAllFunctionExpressions(HsqlList set) {
Expression.collectAllExpressions(set, this,
Expression.emptyExpressionSet,
Expression.emptyExpressionSet);
}
/**
* collect all extrassions of a set of expression types appearing anywhere
* in a select statement and its subselects, etc.
*/
static void collectAllExpressions(HsqlList set, Expression e,
OrderedIntHashSet typeSet,
OrderedIntHashSet stopAtTypeSet) {
if (e == null) {
return;
}
if (stopAtTypeSet.contains(e.opType)) {
return;
}
for (int i = 0; i < e.nodes.length; i++) {
collectAllExpressions(set, e.nodes[i], typeSet, stopAtTypeSet);
}
if (typeSet.contains(e.opType)) {
set.add(e);
}
if (e.subQuery != null && e.subQuery.queryExpression != null) {
e.subQuery.queryExpression.collectAllExpressions(set, typeSet,
stopAtTypeSet);
}
}
/**
* isCorrelated
*/
public boolean isCorrelated() {
if (opType == OpTypes.TABLE_SUBQUERY && subQuery != null
&& subQuery.isCorrelated()) {
return true;
}
return false;
}
/**
* checkValidCheckConstraint
*/
public void checkValidCheckConstraint() {
HsqlArrayList set = new HsqlArrayList();
Expression.collectAllExpressions(set, this, subqueryExpressionSet,
emptyExpressionSet);
if (!set.isEmpty()) {
throw Error.error(ErrorCode.X_0A000,
"subquery in check constraint");
}
}
static HsqlList resolveColumnSet(RangeVariable[] rangeVars,
HsqlList sourceSet, HsqlList targetSet) {
if (sourceSet == null) {
return targetSet;
}
for (int i = 0; i < sourceSet.size(); i++) {
Expression e = (Expression) sourceSet.get(i);
targetSet = e.resolveColumnReferences(rangeVars, targetSet);
}
return targetSet;
}
Expression getIndexableExpression(RangeVariable rangeVar) {
return null;
}
/************************* Volt DB Extensions *************************/
// A VoltDB extension to support indexed expressions
public void collectAllColumnExpressions(HsqlList set) {
Expression.collectAllExpressions(set, this,
Expression.columnExpressionSet,
Expression.emptyExpressionSet);
}
static Map<Integer, VoltXMLElement> prototypes = new HashMap<>();
static {
prototypes.put(OpTypes.VALUE, new VoltXMLElement("value")); // constant value
prototypes.put(OpTypes.COLUMN, new VoltXMLElement("columnref")); // reference
prototypes.put(OpTypes.COALESCE, (new VoltXMLElement("operation")).withValue("optype", "operator_case_when"));
prototypes.put(OpTypes.DEFAULT, new VoltXMLElement("columnref")); // uninteresting!? ExpressionColumn
prototypes.put(OpTypes.SIMPLE_COLUMN, (new VoltXMLElement("simplecolumn")));
prototypes.put(OpTypes.VARIABLE, null); // Some kind of HSQL session parameter? --paul
prototypes.put(OpTypes.PARAMETER, null); // Some kind of HSQL session parameter? --paul
prototypes.put(OpTypes.DYNAMIC_PARAM, (new VoltXMLElement("value")).withValue("isparam", "true")); // param
prototypes.put(OpTypes.ASTERISK, new VoltXMLElement("asterisk"));
prototypes.put(OpTypes.SEQUENCE, null); // not yet supported sequence type
prototypes.put(OpTypes.SCALAR_SUBQUERY,null); // not yet supported subquery feature, query based row/table
prototypes.put(OpTypes.ROW_SUBQUERY, null); // not yet supported subquery feature
prototypes.put(OpTypes.TABLE_SUBQUERY,new VoltXMLElement("tablesubquery"));
prototypes.put(OpTypes.ROW, new VoltXMLElement("row")); // rows
prototypes.put(OpTypes.TABLE, new VoltXMLElement("table")); // not yet supported subquery feature, but needed for "in"
prototypes.put(OpTypes.FUNCTION, null); // not used (HSQL user-defined functions).
prototypes.put(OpTypes.SQL_FUNCTION, new VoltXMLElement("function"));
prototypes.put(OpTypes.ROUTINE_FUNCTION, null); // not used
//arithmetic operations
prototypes.put(OpTypes.NEGATE, (new VoltXMLElement("operation")).withValue("optype", "negate"));
prototypes.put(OpTypes.ADD, (new VoltXMLElement("operation")).withValue("optype", "add"));
prototypes.put(OpTypes.SUBTRACT, (new VoltXMLElement("operation")).withValue("optype", "subtract"));
prototypes.put(OpTypes.MULTIPLY, (new VoltXMLElement("operation")).withValue("optype", "multiply"));
prototypes.put(OpTypes.DIVIDE, (new VoltXMLElement("operation")).withValue("optype", "divide"));
prototypes.put(OpTypes.CONCAT, (new VoltXMLElement("function")) // concatenation
.withValue("function_id", FunctionCustom.FUNC_CONCAT_ID_STRING)
.withValue("name", Tokens.T_CONCAT_WORD)
.withValue("valuetype", Type.SQL_VARCHAR.getNameString()));
// logicals - comparisons
prototypes.put(OpTypes.EQUAL, (new VoltXMLElement("operation")).withValue("optype", "equal"));
prototypes.put(OpTypes.GREATER_EQUAL, (new VoltXMLElement("operation")).withValue("optype", "greaterthanorequalto"));
prototypes.put(OpTypes.GREATER, (new VoltXMLElement("operation")).withValue("optype", "greaterthan"));
prototypes.put(OpTypes.SMALLER, (new VoltXMLElement("operation")).withValue("optype", "lessthan"));
prototypes.put(OpTypes.SMALLER_EQUAL, (new VoltXMLElement("operation")).withValue("optype", "lessthanorequalto"));
prototypes.put(OpTypes.NOT_EQUAL, (new VoltXMLElement("operation")).withValue("optype", "notequal"));
prototypes.put(OpTypes.IS_NULL, (new VoltXMLElement("operation")).withValue("optype", "is_null"));
// logicals - operations
prototypes.put(OpTypes.NOT, (new VoltXMLElement("operation")).withValue("optype", "not"));
prototypes.put(OpTypes.AND, (new VoltXMLElement("operation")).withValue("optype", "and"));
prototypes.put(OpTypes.OR, (new VoltXMLElement("operation")).withValue("optype", "or"));
// logicals - quantified comparison
prototypes.put(OpTypes.ALL_QUANTIFIED,null); // not used -- an ExpressionLogical exprSubType value only
prototypes.put(OpTypes.ANY_QUANTIFIED,null); // not used -- an ExpressionLogical exprSubType value only
// logicals - other predicates
prototypes.put(OpTypes.LIKE, (new VoltXMLElement("operation")).withValue("optype", "like"));
prototypes.put(OpTypes.IN, null); // not yet supported ExpressionLogical
prototypes.put(OpTypes.EXISTS, (new VoltXMLElement("operation")).withValue("optype", "exists"));
prototypes.put(OpTypes.OVERLAPS, null); // not yet supported ExpressionLogical
prototypes.put(OpTypes.UNIQUE, null); // not yet supported ExpressionLogical
prototypes.put(OpTypes.NOT_DISTINCT, (new VoltXMLElement("operation")).withValue("optype", "notdistinct"));
prototypes.put(OpTypes.MATCH_SIMPLE, null); // not yet supported ExpressionLogical
prototypes.put(OpTypes.MATCH_PARTIAL, null); // not yet supported ExpressionLogical
prototypes.put(OpTypes.MATCH_FULL, null); // not yet supported ExpressionLogical
prototypes.put(OpTypes.MATCH_UNIQUE_SIMPLE, null); // not yet supported ExpressionLogical
prototypes.put(OpTypes.MATCH_UNIQUE_PARTIAL, null); // not yet supported ExpressionLogical
prototypes.put(OpTypes.MATCH_UNIQUE_FULL, null); // not yet supported ExpressionLogical
// unwindowed aggregate functions
prototypes.put(OpTypes.COUNT, (new VoltXMLElement("aggregation")).withValue("optype", "count"));
prototypes.put(OpTypes.APPROX_COUNT_DISTINCT, (new VoltXMLElement("aggregation")).withValue("optype", "approx_count_distinct"));
prototypes.put(OpTypes.SUM, (new VoltXMLElement("aggregation")).withValue("optype", "sum"));
prototypes.put(OpTypes.MIN, (new VoltXMLElement("aggregation")).withValue("optype", "min"));
prototypes.put(OpTypes.MAX, (new VoltXMLElement("aggregation")).withValue("optype", "max"));
prototypes.put(OpTypes.AVG, (new VoltXMLElement("aggregation")).withValue("optype", "avg"));
prototypes.put(OpTypes.EVERY, (new VoltXMLElement("aggregation")).withValue("optype", "every"));
prototypes.put(OpTypes.SOME, (new VoltXMLElement("aggregation")).withValue("optype", "some"));
prototypes.put(OpTypes.STDDEV_POP, (new VoltXMLElement("aggregation")).withValue("optype", "stddevpop"));
prototypes.put(OpTypes.STDDEV_SAMP, (new VoltXMLElement("aggregation")).withValue("optype", "stddevsamp"));
prototypes.put(OpTypes.VAR_POP, (new VoltXMLElement("aggregation")).withValue("optype", "varpop"));
prototypes.put(OpTypes.VAR_SAMP, (new VoltXMLElement("aggregation")).withValue("optype", "varsamp"));
// windowed aggregate functions
prototypes.put(OpTypes.WINDOWED_RANK, (new VoltXMLElement("win_aggregation")).withValue("optype", "windowed_rank"));
prototypes.put(OpTypes.WINDOWED_DENSE_RANK, (new VoltXMLElement("win_aggregation")).withValue("optype", "windowed_dense_rank"));
prototypes.put(OpTypes.WINDOWED_COUNT,(new VoltXMLElement("win_aggregation")).withValue("optype", "windowed_count"));
prototypes.put(OpTypes.WINDOWED_MAX, (new VoltXMLElement("win_aggregation")).withValue("optype", "windowed_max"));
prototypes.put(OpTypes.WINDOWED_MIN, (new VoltXMLElement("win_aggregation")).withValue("optype", "windowed_min"));
prototypes.put(OpTypes.WINDOWED_SUM, (new VoltXMLElement("win_aggregation")).withValue("optype", "windowed_sum"));
// other operations
prototypes.put(OpTypes.CAST, (new VoltXMLElement("operation")).withValue("optype", "cast"));
prototypes.put(OpTypes.CASEWHEN, (new VoltXMLElement("operation")).withValue("optype", "operator_case_when"));
prototypes.put(OpTypes.ORDER_BY, new VoltXMLElement("orderby"));
prototypes.put(OpTypes.LIMIT, new VoltXMLElement("limit"));
prototypes.put(OpTypes.ALTERNATIVE, (new VoltXMLElement("operation")).withValue("optype", "operator_alternative"));
prototypes.put(OpTypes.ZONE_MODIFIER, null); // ???
prototypes.put(OpTypes.MULTICOLUMN, null); // an uninteresting!? ExpressionColumn case
}
/**
* A SimpleColumnContext encapsulates the current Session object and
* (optionally) select statement display columns and related state that may
* be needed to resolve some column expressions in a statement's expression
* trees. It is also used to tag pseudo-display-columns that are used solely
* to resolve column expressions but are NOT actually intended for "display"
* in the query result set. A SimpleColumnContext is the main vehicle for
* passing shared context into the recursive Expression.voltGetXML process.
**/
static class SimpleColumnContext {
final Session m_session;
final List<Expression> m_displayCols;
final java.util.Set<Integer> m_ignoredDisplayColIndexes = new java.util.HashSet<>();
private int m_startKey = -1;
SimpleColumnContext(Session session, List<Expression> displayCols) {
m_session = session;
m_displayCols = displayCols;
}
SimpleColumnContext withStartKey(int startKey) {
m_startKey = startKey;
return this;
}
private VoltXMLElement resolveSimpleColumn(Expression simpleCol)
throws HSQLParseException {
if (m_displayCols == null) {
if (simpleCol instanceof ExpressionColumn == false) {
throw new HSQLParseException(
"VoltDB does not support this complex query currently.");
}
}
else {
// find the substitute from displayCols list
for (int ii=m_startKey+1; ii < m_displayCols.size(); ++ii) {
Expression otherCol = m_displayCols.get(ii);
// This mechanism of finding the expression that a SIMPLE_COLUMN
// is referring to is inherently fragile---columnIndex is an
// offset into different things depending on context!
if (otherCol != null && (otherCol.opType != OpTypes.SIMPLE_COLUMN) &&
(otherCol.columnIndex == simpleCol.columnIndex) &&
!(otherCol instanceof ExpressionColumn)) {
// Prepare to skip displayCols that are the referent of a SIMPLE_COLUMN.
m_ignoredDisplayColIndexes.add(ii);
// Serialize the column this simple column stands-in for.
return otherCol.voltGetXML(this, simpleCol.getAlias());
}
}
}
return null;
}
public boolean disabledTheColumnForDisplay(int jj) {
return m_ignoredDisplayColIndexes.contains(jj);
}
}
/**
* @param session
* @return
* @throws HSQLParseException
*/
VoltXMLElement voltGetXML(Session session) throws HSQLParseException {
return voltGetXML(new SimpleColumnContext(session, null), null);
}
/**
* VoltDB added method to get a non-catalog-dependent
* representation of this HSQLDB object.
* @param context The context encapsulates the current Session object and (optionally)
* select statement display columns that may be needed to resolve some names.
* @return A VoltXML tree structure.
* @throws HSQLParseException
*/
VoltXMLElement voltGetXML(SimpleColumnContext context, String realAlias)
throws HSQLParseException {
// The voltXML representations of expressions tends to be driven much more by the expression's opType
// than its Expression class.
int exprOp = getType();
// The opType value of "SIMPLE_COLUMN" is a special case that spans Expression classes and seems to
// need to use the Expression's exact class to be able to correctly determine its VoltXMLElement
// representation.
// Last minute "SIMPLE_COLUMN" substitutions can blast a new opType into an Expression of a class
// other than ExpressionColumn as an optimization for duplicated expressions.
// VoltDB currently uses "alias" matching to navigate to the correct (duplicate) expression structure
// typically an ExpressionAggregate.
// The prototypes dictionary is set up to handle a SIMPLE_COLUMN of any class EXCEPT ExpressionColumn.
// A SIMPLE_COLUMN ExpressionColumn can be treated as a normal "COLUMN" ExpressionColumn.
// That case gets explicitly enabled here by fudging the opType from SIMPLE_COLUMN to COLUMN.
if (exprOp == OpTypes.SIMPLE_COLUMN) {
VoltXMLElement asResolved = context.resolveSimpleColumn(this);
if (asResolved != null) {
return asResolved;
}
// ENG-10429 moved the following two lines here. See fix note https://issues.voltdb.com/browse/ENG-10429.
// convert the SIMPLE_COLUMN into a COLUMN
opType = OpTypes.COLUMN;
exprOp = OpTypes.COLUMN;
}
// Use the opType to find a pre-initialized prototype VoltXMLElement with the correct
// name and any required hard-coded values pre-set.
VoltXMLElement exp = prototypes.get(exprOp);
if (exp == null) {
// Must have found an unsupported opType.
throwForUnsupportedExpression(exprOp);
}
// Duplicate the prototype and add any expression particulars needed for the specific opType value,
// as well as a unique identifier, a possible alias, and child nodes.
exp = exp.duplicate();
exp.attributes.put("id", getUniqueId(context.m_session));
if (realAlias != null) {
exp.attributes.put("alias", realAlias);
}
else if ((alias != null) && (getAlias().length() > 0)) {
exp.attributes.put("alias", getAlias());
}
// Add expression sub type
if (exprSubType == OpTypes.ANY_QUANTIFIED) {
exp.attributes.put("opsubtype", "any");
}
else if (exprSubType == OpTypes.ALL_QUANTIFIED) {
exp.attributes.put("opsubtype", "all");
}
for (Expression expr : nodes) {
if (expr != null) {
VoltXMLElement vxmle = expr.voltGetXML(context, null);
exp.children.add(vxmle);
assert(vxmle != null);
}
}
// Few opTypes need additional special case detailing or special case error detection.
// Very few need access to members defined on specific Expression classes, but they
// can usually be accessed via down-casting.
// Even fewer need private members, and they are accessed by delegation to a
// class-specific voltAnnotate... member function that directly manipulates the
// VoltXMLElement.
switch (exprOp) {
case OpTypes.VALUE:
// Apparently at this stage, all valid non-NULL values must have a
// type determined by HSQL.
// (I'm not sure why this MUST be the case --paul.)
if (valueData == null) {
String valueType = (dataType == null) ? "NULL" :
Types.getTypeName(dataType.typeCode);
exp.attributes.put("valuetype", valueType);
return exp;
}
exp.attributes.put("valuetype", Types.getTypeName(dataType.typeCode));
if (valueData instanceof TimestampData) {
// When we get the default from the DDL,
// it gets jammed into a TimestampData object. If we
// don't do this, we get a Java class/reference
// string in the output schema for the DDL.
// EL HACKO: I'm just adding in the timezone seconds
// at the moment, hope this is right --izzy
TimestampData time = (TimestampData) valueData;
exp.attributes.put("value", Long.toString(Math.round((time.getSeconds() +
time.getZone()) * 1e6) +
time.getNanos() / 1000));
return exp;
}
// convert binary values to hex
if (valueData instanceof BinaryData) {
BinaryData bd = (BinaryData) valueData;
exp.attributes.put("value", hexEncode(bd.getBytes()));
return exp;
}
// Otherwise just string format the value.
if (dataType instanceof NumberType && ! dataType.isIntegralType()) {
// remove the scentific exponent notation
exp.attributes.put("value", new BigDecimal(valueData.toString()).toPlainString());
return exp;
}
exp.attributes.put("value", valueData.toString());
return exp;
case OpTypes.COLUMN:
ExpressionColumn ec = (ExpressionColumn)this;
return ec.voltAnnotateColumnXML(exp);
case OpTypes.COALESCE:
return convertUsingColumnrefToCoaleseExpression(context.m_session, exp, dataType);
case OpTypes.SQL_FUNCTION:
FunctionSQL fn = (FunctionSQL)this;
return fn.voltAnnotateFunctionXML(exp);
case OpTypes.COUNT:
case OpTypes.SUM:
case OpTypes.AVG:
if (((ExpressionAggregate)this).isDistinctAggregate) {
exp.attributes.put("distinct", "true");
}
return exp;
case OpTypes.ORDER_BY:
if (((ExpressionOrderBy)this).isDescending()) {
exp.attributes.put("desc", "true");
}
return exp;
case OpTypes.CAST:
if (dataType == null) {
throw new HSQLParseException(
"VoltDB could not determine the type in a CAST operation");
}
exp.attributes.put("valuetype", dataType.getNameString());
return exp;
case OpTypes.TABLE_SUBQUERY:
if (subQuery == null || subQuery.queryExpression == null) {
throw new HSQLParseException("VoltDB could not determine the subquery");
}
ExpressionColumn parameters[] = new ExpressionColumn[0];
exp.children.add(StatementQuery.voltGetXMLExpression(subQuery.queryExpression,
parameters, context.m_session));
return exp;
case OpTypes.ALTERNATIVE:
assert(nodes.length == 2);
// If with ELSE clause, pad NULL with it.
if (nodes[RIGHT] instanceof ExpressionValue) {
ExpressionValue val = (ExpressionValue) nodes[RIGHT];
if (val.valueData == null && val.dataType == Type.SQL_ALL_TYPES) {
exp.children.get(RIGHT).attributes.put("valuetype", dataType.getNameString());
}
}
case OpTypes.CASEWHEN:
// Hsql has check dataType can not be null.
assert(dataType != null);
exp.attributes.put("valuetype", dataType.getNameString());
return exp;
case OpTypes.WINDOWED_RANK:
case OpTypes.WINDOWED_DENSE_RANK:
case OpTypes.WINDOWED_COUNT:
case OpTypes.WINDOWED_MIN:
case OpTypes.WINDOWED_MAX:
case OpTypes.WINDOWED_SUM:
assert(dataType != null);
assert(this instanceof ExpressionWindowed);
exp.attributes.put("valuetype", dataType.getNameString());
ExpressionWindowed erank = (ExpressionWindowed) this;
return erank.voltAnnotateWindowedAggregateXML(exp, context);
default:
return exp;
}
}
private static final int caseDiff = ('a' - 'A');
/**
*
* @param data A binary array of bytes.
* @return A hex-encoded string with double length.
*/
public static String hexEncode(byte[] data) {
if (data == null) {
return null;
}
StringBuilder sb = new StringBuilder();
for (byte b : data) {
// hex encoding same way as java.net.URLEncoder.
char ch = Character.forDigit((b >> 4) & 0xF, 16);
// to uppercase
if (Character.isLetter(ch)) {
ch -= caseDiff;
}
sb.append(ch);
ch = Character.forDigit(b & 0xF, 16);
if (Character.isLetter(ch)) {
ch -= caseDiff;
}
sb.append(ch);
}
return sb.toString();
}
private static void throwForUnsupportedExpression(int exprOp)
throws HSQLParseException {
String opAsString;
switch (exprOp) {
//case OpTypes.COALESCE:
// opAsString = "the COALESCE operator. Consider using DECODE."; break; //MAY require ExpressionColumn state
case OpTypes.VARIABLE:
opAsString = "HSQL session variables"; break; // Some kind of HSQL session parameter? --paul
case OpTypes.PARAMETER:
opAsString = "HSQL session parameters"; break; // Some kind of HSQL session parameter? --paul
case OpTypes.SEQUENCE:
opAsString = "sequence types"; break; // not yet supported sequence type
case OpTypes.SCALAR_SUBQUERY:
case OpTypes.ROW_SUBQUERY:
case OpTypes.TABLE_SUBQUERY:
case OpTypes.ROW:
case OpTypes.TABLE:
case OpTypes.EXISTS:
throw new HSQLParseException("Unsupported subquery syntax within an expression. Consider using a join or multiple statements instead");
case OpTypes.FUNCTION: opAsString = "HSQL-style user-defined Java SQL functions"; break;
case OpTypes.ROUTINE_FUNCTION: opAsString = "HSQL routine functions"; break; // not used
case OpTypes.ALL_QUANTIFIED:
case OpTypes.ANY_QUANTIFIED:
opAsString = "sequences or subqueries"; break; // not used -- an ExpressionLogical exprSubType value only
case OpTypes.IN:
opAsString = "the IN operator. Consider using an OR expression"; break; // not yet supported
case OpTypes.OVERLAPS:
case OpTypes.UNIQUE:
case OpTypes.NOT_DISTINCT:
opAsString = "sequences or subqueries"; break; // not yet supported ExpressionLogical
case OpTypes.MATCH_SIMPLE:
case OpTypes.MATCH_PARTIAL:
case OpTypes.MATCH_FULL:
case OpTypes.MATCH_UNIQUE_SIMPLE:
case OpTypes.MATCH_UNIQUE_PARTIAL:
case OpTypes.MATCH_UNIQUE_FULL:
opAsString = "the MATCH operator"; break; // not yet supported ExpressionLogical
case OpTypes.ZONE_MODIFIER:
opAsString = "ZONE modifier operations"; break; // ???
case OpTypes.MULTICOLUMN:
opAsString = "a MULTICOLUMN operation"; break; // an uninteresting!? ExpressionColumn case
default:
opAsString = " the unknown operator with numeric code (" + String.valueOf(exprOp) + ")";
}
throw new HSQLParseException("VoltDB does not support " + opAsString);
}
/**
* VoltDB added method to simplify an expression by eliminating identical subexpressions (same id)
* The original expression must be a logical conjunction of form e1 AND e2 AND e3 AND e4.
* If subexpression e1 is identical to the subexpression e2 the simplified expression would be
* e1 AND e3 AND e4.
* @param session The current Session object may be needed to resolve
* some names.
* @return simplified expression.
*/
public Expression eliminateDuplicates(final Session session) {
// First build the map of child expressions joined by the logical AND
// The key is the expression id and the value is the expression itself
Map<String, Expression> subExprMap = new HashMap<>();
extractAndSubExpressions(session, this, subExprMap);
// Reconstruct the expression
if (!subExprMap.isEmpty()) {
Iterator<Map.Entry<String, Expression>> itExpr = subExprMap.entrySet().iterator();
Expression finalExpr = itExpr.next().getValue();
while (itExpr.hasNext()) {
finalExpr = new ExpressionLogical(OpTypes.AND, finalExpr, itExpr.next().getValue());
}
return finalExpr;
}
return this;
}
protected void extractAndSubExpressions(final Session session, Expression expr,
Map<String, Expression> subExprMap) {
// If it is a logical expression AND then traverse down the tree
if (expr instanceof ExpressionLogical && ((ExpressionLogical) expr).opType == OpTypes.AND) {
extractAndSubExpressions(session, expr.nodes[LEFT], subExprMap);
extractAndSubExpressions(session, expr.nodes[RIGHT], subExprMap);
}
else {
String id = expr.getUniqueId(session);
subExprMap.put(id, expr);
}
}
protected String cached_id = null;
/**
* Get the hex address of this Expression Object in memory,
* to be used as a unique identifier.
* @return The hex address of the pointer to this object.
*/
protected String getUniqueId(final Session session) {
if (cached_id != null) {
return cached_id;
}
//
// Calculated an new Id
//
// this line ripped from the "describe" method
// seems to help with some types like "equal"
cached_id = new String();
int hashCode = 0;
// If object is a leaf node, then use John's original code...
if (getType() == OpTypes.VALUE || getType() == OpTypes.COLUMN) {
hashCode = super.hashCode();
}
// Otherwise, generate an id based on the children
else {
//
// Horribly inefficient, but it works for now...
//
final List<String> id_list = new Vector<>();
new Object() {
public void traverse(Expression exp) {
for (Expression expr : exp.nodes) {
if (expr != null) {
id_list.add(expr.getUniqueId(session));
}
}
}
}.traverse(this);
if (id_list.size() > 0) {
// Flatten the id list, intern it, and then do the same trick from above
for (String temp : id_list)
cached_id += "+" + temp;
hashCode = cached_id.intern().hashCode();
}
else {
hashCode = super.hashCode();
}
}
long id = session.getNodeIdForExpression(hashCode);
cached_id = Long.toString(id);
return cached_id;
}
// A VoltDB extension to support indexed expressions
public VoltXMLElement voltGetExpressionXML(Session session, Table table)
throws HSQLParseException {
resolveTableColumns(table);
Expression parent = null; // As far as I can tell, this argument just gets passed around but never used !?
resolveTypes(session, parent);
return voltGetXML(new SimpleColumnContext(session, null), null);
}
// A VoltDB extension to support indexed expressions
private void resolveTableColumns(Table table) {
HsqlList set = new HsqlArrayList();
collectAllColumnExpressions(set);
for (int i = 0; i < set.size(); i++) {
ExpressionColumn array_element = (ExpressionColumn)set.get(i);
ColumnSchema column = table.getColumn(table.getColumnIndex(array_element.getAlias()));
array_element.setAttributesAsColumn(column, false);
}
}
// A VoltDB extension to convert columnref expression for a column that is part of the USING clause
// into a COALESCE expression
// columnref operation operator_case_when
// columnref T1.C -> operation is_null
// columnref T2.C columnref T1.C
// operation operator_alternative
// columnref T2.C
// columnref T1.C
private VoltXMLElement convertUsingColumnrefToCoaleseExpression(Session session, VoltXMLElement exp, Type dataType)
throws org.hsqldb_voltpatches.HSQLInterface.HSQLParseException {
// Hsql has check dataType can not be null.
assert(dataType != null);
exp.attributes.put("valuetype", dataType.getNameString());
// Extract unique columnref
HashSet<String> tables = new HashSet<>();
ArrayDeque<VoltXMLElement> uniqueColumnrefs = new ArrayDeque<>();
for (VoltXMLElement columnref : exp.children) {
String table = columnref.attributes.get("table");
String tableAlias = columnref.attributes.get("tablealias");
assert (table != null);
String tableOrAlias = (tableAlias == null) ? table : tableAlias;
if (tables.contains(tableOrAlias)) {
continue;
}
tables.add(tableOrAlias);
uniqueColumnrefs.add(columnref);
}
// Delete original children
exp.children.clear();
// There should be at least 2 columnref expressions
assert(uniqueColumnrefs.size() > 1);
VoltXMLElement lastAlternativeExpr = null;
VoltXMLElement resultColaesceExpr = null;
while (true) {
VoltXMLElement next = uniqueColumnrefs.pop();
if (uniqueColumnrefs.isEmpty()) {
// Last columnref. Simply plug it in to the last THEN Expression
assert(lastAlternativeExpr != null);
// Add next as the first child
lastAlternativeExpr.children.add(0, next);
break;
}
// IS_NULL expression
VoltXMLElement isnull_expr = prototypes.get(OpTypes.IS_NULL);
if (isnull_expr == null) {
throwForUnsupportedExpression(OpTypes.IS_NULL);
}
isnull_expr = isnull_expr.duplicate();
isnull_expr.attributes.put("id", this.getUniqueId(session));
isnull_expr.children.add(next);
// Alternative expression
VoltXMLElement alt_expr = prototypes.get(OpTypes.ALTERNATIVE);
if (alt_expr == null) {
throwForUnsupportedExpression(OpTypes.ALTERNATIVE);
}
alt_expr = alt_expr.duplicate();
alt_expr.attributes.put("id", this.getUniqueId(session));
alt_expr.attributes.put("valuetype", dataType.getNameString());
// The next expression should be a second child
// but for now we keep it as the first one
alt_expr.children.add(next);
// COALESCE expression
VoltXMLElement coalesceExpr = exp.duplicate();
coalesceExpr.attributes.put("alias", next.attributes.get("alias"));
coalesceExpr.attributes.put("column", next.attributes.get("column"));
// Add IS NULL and ALTERNATIVE expressions to the coalesceExpr
coalesceExpr.children.add(isnull_expr);
coalesceExpr.children.add(alt_expr);
if (resultColaesceExpr == null) {
resultColaesceExpr = coalesceExpr;
} else {
assert(lastAlternativeExpr != null);
// Add coalesceExpr as the first child to the last alternative expression
lastAlternativeExpr.children.add(0, coalesceExpr);
}
lastAlternativeExpr = alt_expr;
}
assert(resultColaesceExpr != null);
return resultColaesceExpr;
}
/**
* This ugly code is never called by HSQL or VoltDB
* explicitly, but it does make debugging in eclipse
* easier because it makes expressions display their
* type when you mouse over them.
*/
@Override
public String toString() {
String type = null;
// iterate through all optypes, looking for
// a match...
// sadly do this with reflection
Field[] fields = OpTypes.class.getFields();
for (Field f : fields) {
if (f.getType() != int.class) {
continue;
}
int value = 0;
try {
value = f.getInt(null);
}
catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// found a match
if (value == opType) {
type = f.getName();
break;
}
}
assert(type != null);
// return the original default impl + the type
String str = super.toString() + " with opType " + type +
", isAggregate: " + isAggregate +
", columnIndex: " + columnIndex;
if (this instanceof ExpressionOrderBy) {
str += "\n " + nodes[LEFT].toString();
}
return str;
}
}