/* 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.util.Comparator;
import org.hsqldb_voltpatches.HsqlNameManager.HsqlName;
import org.hsqldb_voltpatches.HsqlNameManager.SimpleName;
import org.hsqldb_voltpatches.lib.ArrayListIdentity;
import org.hsqldb_voltpatches.lib.HsqlList;
import org.hsqldb_voltpatches.lib.OrderedHashSet;
import org.hsqldb_voltpatches.lib.Set;
import org.hsqldb_voltpatches.types.Type;
/**
* Implementation of column, variable, parameter, etc. access operations.
*
* @author Fred Toussi (fredt@users dot sourceforge.net)
* @version 1.9.0
* @since 1.9.0
*/
public class ExpressionColumn extends Expression {
private static final ColumnComparator m_comparator = new ColumnComparator();
public final static ExpressionColumn[] emptyArray =
new ExpressionColumn[]{};
//
ColumnSchema column;
String schema;
String tableName;
String columnName;
RangeVariable rangeVariable;
//
NumberSequence sequence;
boolean isWritable; // = false; true if column of writable table
/**
* Creates a OpCodes.COLUMN expression
*/
ExpressionColumn(String schema, String table, String column) {
super(OpTypes.COLUMN);
this.schema = schema;
tableName = table;
columnName = column;
}
ExpressionColumn(ColumnSchema column) {
super(OpTypes.COLUMN);
columnName = column.getName().name;
}
ExpressionColumn(RangeVariable rangeVar, ColumnSchema column) {
super(OpTypes.COLUMN);
setAttributesAsColumn(rangeVar,
rangeVar.rangeTable.findColumn(column.getName().name));
}
ExpressionColumn(RangeVariable rangeVar, ColumnSchema column, int index) {
super(OpTypes.COLUMN);
setAttributesAsColumn(rangeVar, index);
}
/**
* Creates a temporary OpCodes.COLUMN expression
*/
ExpressionColumn(Expression e, int colIndex, int rangePosition) {
this(OpTypes.SIMPLE_COLUMN);
dataType = e.dataType;
columnIndex = colIndex;
alias = e.alias;
this.rangePosition = rangePosition;
}
ExpressionColumn() {
super(OpTypes.ASTERISK);
}
ExpressionColumn(int type) {
super(type);
if (type == OpTypes.DYNAMIC_PARAM) {
isParam = true;
}
}
ExpressionColumn(Expression[] nodes, String name) {
super(OpTypes.COALESCE);
this.nodes = nodes;
this.columnName = name;
}
/**
* Creates an OpCodes.ASTERISK expression
*/
ExpressionColumn(String schema, String table) {
super(OpTypes.MULTICOLUMN);
this.schema = schema;
tableName = table;
}
/**
* Creates a OpCodes.SEQUENCE expression
*/
ExpressionColumn(NumberSequence sequence) {
super(OpTypes.SEQUENCE);
this.sequence = sequence;
dataType = sequence.getDataType();
}
void setAttributesAsColumn(RangeVariable range, int index) {
columnIndex = index;
column = range.getColumn(index);
dataType = column.getDataType();
rangeVariable = range;
// Note: rangeVariable.variables is only non-null in the
// special case of a RangeVariable that is rigged to contain
// system settings.
// There's a good chance that this special case is not
// exercised by VoltDB.
if (rangeVariable.variables != null) {
return;
}
columnName = column.getName().name;
Table table = range.getTable();
tableName = table.getName().name;
schema = table.getSchemaName().name;
if (alias == null && rangeVariable.hasColumnAliases()) {
alias = rangeVariable.getColumnAliasName(index);
}
rangeVariable.addColumn(columnIndex);
}
@Override
void setAttributesAsColumn(ColumnSchema column, boolean isWritable) {
this.column = column;
dataType = column.getDataType();
this.isWritable = isWritable;
}
@Override
SimpleName getSimpleName() {
if (alias != null) {
return alias;
}
if (column != null) {
return column.getName();
}
if (opType == OpTypes.COALESCE) {
return nodes[LEFT].getSimpleName();
}
return null;
}
@Override
String getAlias() {
if (alias != null) {
return alias.name;
}
if (opType == OpTypes.COLUMN) {
return columnName;
}
if (opType == OpTypes.COALESCE) {
return columnName;
}
return "";
}
public String getBaseColumnName() {
if (opType == OpTypes.COLUMN && rangeVariable != null) {
return rangeVariable.getTable().getColumn(
columnIndex).getName().name;
}
return null;
}
public HsqlName getBaseColumnHsqlName() {
return column.getName();
}
@Override
void collectObjectNames(Set set) {
// BEGIN Cherry-picked code change from hsqldb-2.3.2
switch (opType) {
case OpTypes.SEQUENCE :
HsqlName name = sequence.getName();
set.add(name);
return;
case OpTypes.MULTICOLUMN :
case OpTypes.DYNAMIC_PARAM :
case OpTypes.ASTERISK :
case OpTypes.SIMPLE_COLUMN :
case OpTypes.COALESCE :
break;
case OpTypes.PARAMETER :
case OpTypes.VARIABLE :
break;
case OpTypes.COLUMN :
set.add(column.getName());
if (column.getName().parent != null) {
set.add(column.getName().parent);
}
return;
}
/* Disable 13 lines
if (opType == OpTypes.SEQUENCE) {
HsqlName name = ((NumberSequence) valueData).getName();
set.add(name);
return;
}
set.add(column.getName());
if (column.getName().parent != null) {
set.add(column.getName().parent);
}
... disabled 13 lines */
// END Cherry-picked code change from hsqldb-2.3.2
}
@Override
String getColumnName() {
if (opType == OpTypes.COLUMN && column != null) {
return column.getName().name;
}
return getAlias();
}
@Override
ColumnSchema getColumn() {
return column;
}
String getSchemaName() {
return schema;
}
@Override
RangeVariable getRangeVariable() {
return rangeVariable;
}
@Override
public HsqlList resolveColumnReferences(RangeVariable[] rangeVarArray,
int rangeCount, HsqlList unresolvedSet, boolean acceptsSequences) {
switch (opType) {
case OpTypes.SEQUENCE :
if (!acceptsSequences) {
throw Error.error(ErrorCode.X_42598);
}
break;
case OpTypes.MULTICOLUMN :
case OpTypes.DYNAMIC_PARAM :
case OpTypes.ASTERISK :
case OpTypes.SIMPLE_COLUMN :
case OpTypes.COALESCE :
break;
case OpTypes.PARAMETER :
case OpTypes.VARIABLE :
case OpTypes.COLUMN :
if (rangeVariable != null) {
return unresolvedSet;
}
// Look in all the range variables. We may
// find this column more than once, and that
// would be an error See ENG-9367.
//
// Note that we can't actually commit to a resolution
// until we have looked everywhere. This means we need to
// store up potential resolutions until we have looked at all
// the range variables. If we find just one, we finally
// resolve it. below.
java.util.Set<ColumnReferenceResolution> usingResolutions
= new java.util.TreeSet<>(m_comparator);
java.util.Set<ColumnReferenceResolution> rangeVariableResolutions
= new java.util.TreeSet<>(m_comparator);
ColumnReferenceResolution lastRes = null;
int foundSize = 0;
for (int i = 0; i < rangeCount; i++) {
RangeVariable rangeVar = rangeVarArray[i];
if (rangeVar == null) {
continue;
}
ColumnReferenceResolution resolution = resolveColumnReference(rangeVar);
if (resolution != null) {
if (resolution instanceof ExpressionColumnReferenceResolution) {
if (usingResolutions.add(resolution)) {
foundSize += 1;
}
}
else {
assert(resolution instanceof RangeVariableColumnReferenceResolution);
if (rangeVariableResolutions.add(resolution)) {
foundSize += 1;
}
}
// Cache this in case this is the only resolution.
lastRes = resolution;
}
}
if (foundSize == 1) {
lastRes.finallyResolve();
return unresolvedSet;
}
if (foundSize > 1) {
StringBuffer sb = new StringBuffer();
sb.append(String.format("Column \"%s\" is ambiguous. It's in tables: ", columnName));
String sep = "";
// Note: The resolution sets are TreeSets. So we can iterate over them
// in name order.
if (usingResolutions.size() > 0) {
sb.append("USING(");
appendNameList(sb, usingResolutions, "");
sb.append(")");
sep = ", ";
}
appendNameList(sb, rangeVariableResolutions, sep);
throw new HsqlException(sb.toString(), "", 0);
}
// If we get here we didn't find anything. So, add this expression
// to the unresolved set.
if (unresolvedSet == null) {
unresolvedSet = new ArrayListIdentity();
}
unresolvedSet.add(this);
}
// IF we got to here, return the set of unresolved columns.
return unresolvedSet;
}
/*
* Append the names of all the elements in the set of resolutions to the
* string buffer. This is only used for error messages.
*/
private <T> void appendNameList(StringBuffer sb,
java.util.Set<T> resolutions,
String sep) {
for (T oneRes : resolutions) {
sb.append(sep).append(oneRes.toString());
sep = ", ";
}
}
/**
* Return a sort of closure which is useful for resolving a column reference.
* The column reference is either an expression or a column in a table, which
* is named by a range variable. We keep the expression or the
* range variable/column index pair here. We may have several resolutions
* if the column reference is ambiguous. We can't actually commit to one
* until we have examined all of them. So, we defer changing this object
* until we are more sure of the reference.
*
* We store these in a java.util.TreeSet. So we need to have our own
* notion of equality.
*/
private interface ColumnReferenceResolution {
/**
* This is the important operation for this interface. This
* member function calculates the final resolution. We call this after we have
* verified that there is only one possible resolution for this
* column name.
*/
public void finallyResolve();
}
private static class ColumnComparator implements Comparator<ColumnReferenceResolution> {
@Override
public int compare(ColumnReferenceResolution o1, ColumnReferenceResolution o2) {
String n1 = o1.toString();
String n2 = o2.toString();
return n1.compareTo(n2);
}
}
/**
* This class implements the interface for expression columns.
* An expression column is created for a "USING(C)" join condition.
* In this case, the expression will be an ExpressionColumn referencing
* the column "C", which presumably is a column common to two joined
* tables.
*/
private class ExpressionColumnReferenceResolution implements ColumnReferenceResolution {
Expression m_expr;
private static final String m_unknownColumnName = "UnknownColumnName";
public ExpressionColumnReferenceResolution(Expression expr) {
assert(expr != null);
assert(expr instanceof ExpressionColumn);
m_expr = expr;
}
@Override
public void finallyResolve() {
opType = m_expr.opType;
nodes = m_expr.nodes;
dataType = m_expr.dataType;
}
@Override
public String toString() {
ExpressionColumn ec = (ExpressionColumn)m_expr;
if (ec.alias != null && ec.alias.name != null) {
return ec.alias.name;
}
if (ec.columnName != null) {
return ec.columnName;
}
/*
* This should never happen. We should always have an
* alias, or at least a column name. After all, this will
* have been built with "USING(C)" where "C" is a column
* name.
*/
return m_unknownColumnName;
}
}
private class RangeVariableColumnReferenceResolution implements ColumnReferenceResolution {
final RangeVariable m_rangeVariable;
final int m_colIndex;
final int m_replacementOpType;
private final static String m_unknownTableName = "UnknownTable";
public RangeVariableColumnReferenceResolution(RangeVariable rangeVariable,
int colIndex, int replacementOpType) {
assert(rangeVariable != null && 0 <= colIndex);
m_rangeVariable = rangeVariable;
m_colIndex = colIndex;
m_replacementOpType = replacementOpType;
}
@Override
public void finallyResolve() {
setAttributesAsColumn(m_rangeVariable, m_colIndex);
opType = m_replacementOpType;
}
@Override
public String toString() {
// We prefer to use aliases. If we can't find an
// alias, we use the table name.
if (m_rangeVariable.tableAlias != null && m_rangeVariable.tableAlias.name != null) {
return m_rangeVariable.tableAlias.name;
}
if (m_rangeVariable.getTable() != null
&& m_rangeVariable.getTable().getName() != null
&& m_rangeVariable.getTable().getName().name != null) {
return m_rangeVariable.getTable().getName().name;
}
return m_unknownTableName;
}
}
public ColumnReferenceResolution resolveColumnReference(RangeVariable rangeVar) {
if (tableName == null) {
Expression e = rangeVar.getColumnExpression(columnName);
if (e != null) {
return new ExpressionColumnReferenceResolution(e);
}
if (rangeVar.variables != null) {
int colIndex = rangeVar.findColumn(tableName, columnName);
if (colIndex == -1) {
return null;
}
ColumnSchema column = rangeVar.getColumn(colIndex);
if (column.getParameterMode()
== SchemaObject.ParameterModes.PARAM_OUT) {
return null;
}
int replacementOpType = rangeVar.isVariable ? OpTypes.VARIABLE
: OpTypes.PARAMETER;
return new RangeVariableColumnReferenceResolution(
rangeVar, colIndex, replacementOpType);
}
}
if (!rangeVar.resolvesTableName(this)) {
return null;
}
int colIndex = rangeVar.findColumn(tableName, columnName);
if (colIndex == -1) {
return null;
}
return new RangeVariableColumnReferenceResolution(rangeVar, colIndex, opType);
}
@Override
public void resolveTypes(Session session, Expression parent) {
switch (opType) {
case OpTypes.DEFAULT :
if (parent != null && parent.opType != OpTypes.ROW) {
throw Error.error(ErrorCode.X_42544);
}
break;
case OpTypes.COALESCE : {
Type type = null;
for (int i = 0; i < nodes.length; i++) {
type = Type.getAggregateType(nodes[i].dataType, type);
}
dataType = type;
break;
}
}
}
@Override
public Object getValue(Session session) {
switch (opType) {
case OpTypes.DEFAULT :
return null;
case OpTypes.VARIABLE : {
return session.sessionContext.routineVariables[columnIndex];
}
case OpTypes.PARAMETER : {
return session.sessionContext.routineArguments[columnIndex];
}
case OpTypes.COLUMN : {
Object[] data =
session.sessionContext
.rangeIterators[rangeVariable.rangePosition]
.getCurrent();
Object value = data[columnIndex];
Type colType = column.getDataType();
if (!dataType.equals(colType)) {
value = dataType.convertToType(session, value, colType);
}
return value;
}
case OpTypes.SIMPLE_COLUMN : {
Object[] data =
session.sessionContext
.rangeIterators[rangePosition].getCurrent();
return data[columnIndex];
}
case OpTypes.COALESCE : {
Object value = null;
for (int i = 0; i < nodes.length; i++) {
value = nodes[i].getValue(session, dataType);
if (value != null) {
return value;
}
}
return value;
}
case OpTypes.DYNAMIC_PARAM : {
return session.sessionContext.dynamicArguments[parameterIndex];
}
case OpTypes.SEQUENCE : {
return session.sessionData.getSequenceValue(sequence);
}
case OpTypes.ASTERISK :
case OpTypes.MULTICOLUMN :
default :
throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
}
}
@Override
public String getSQL() {
switch (opType) {
case OpTypes.DEFAULT :
return Tokens.T_DEFAULT;
case OpTypes.DYNAMIC_PARAM :
return Tokens.T_QUESTION;
case OpTypes.ASTERISK :
return "*";
case OpTypes.COALESCE :
return alias.getStatementName();
case OpTypes.VARIABLE :
case OpTypes.PARAMETER :
case OpTypes.COLUMN : {
if (column == null) {
if (alias != null) {
return alias.getStatementName();
}
return columnName;
}
if (rangeVariable == null || rangeVariable.tableAlias == null) {
return column.getName().getSchemaQualifiedStatementName();
}
StringBuffer sb = new StringBuffer();
sb.append(rangeVariable.tableAlias.getStatementName());
sb.append('.');
sb.append(column.getName().statementName);
return sb.toString();
}
case OpTypes.MULTICOLUMN : {
if (nodes.length == 0) {
return "*";
}
StringBuffer sb = new StringBuffer();
String prefix = "";
for (Expression e : nodes) {
String s = e.getSQL();
sb.append(prefix).append(s);
prefix = ",";
}
return sb.toString();
}
default :
throw Error.runtimeError(ErrorCode.U_S0500, "Expression");
}
}
@Override
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.DEFAULT :
sb.append(Tokens.T_DEFAULT);
break;
case OpTypes.ASTERISK :
sb.append("OpTypes.ASTERISK ");
break;
case OpTypes.VARIABLE :
sb.append("VARIABLE: ");
sb.append(column.getName().name);
break;
case OpTypes.PARAMETER :
sb.append(Tokens.T_PARAMETER).append(": ");
sb.append(column.getName().name);
break;
case OpTypes.COALESCE :
sb.append(Tokens.T_COLUMN).append(": ");
sb.append(columnName);
if (alias != null) {
sb.append(" AS ").append(alias.name);
}
break;
case OpTypes.COLUMN :
sb.append(Tokens.T_COLUMN).append(": ");
sb.append(column.getName().name);
if (alias != null) {
sb.append(" AS ").append(alias.name);
}
sb.append(' ').append(Tokens.T_TABLE).append(": ").append(
tableName);
break;
case OpTypes.DYNAMIC_PARAM :
sb.append("DYNAMIC PARAM: ");
sb.append(", TYPE = ").append((dataType != null) ? dataType.getNameString() : "null");
break;
case OpTypes.SEQUENCE :
sb.append(Tokens.T_SEQUENCE).append(": ");
sb.append(sequence.getName().name);
break;
case OpTypes.MULTICOLUMN :
// shouldn't get here
}
return sb.toString();
}
/**
* Returns the table name for a column expression as a string
* @return table name
*/
String getTableName() {
if (opType == OpTypes.MULTICOLUMN) {
return tableName;
}
if (opType == OpTypes.COLUMN) {
if (rangeVariable == null) {
return tableName;
}
return rangeVariable.getTable().getName().name;
}
return "";
}
static void checkColumnsResolved(HsqlList set) {
if (set != null && !set.isEmpty()) {
Object obj = set.get(0);
if (obj instanceof ExpressionColumn) {
ExpressionColumn e = (ExpressionColumn) obj;
StringBuffer sb = new StringBuffer();
if (e.schema != null) {
sb.append(e.schema + '.');
}
if (e.tableName != null) {
sb.append(e.tableName + '.');
}
throw Error.error(ErrorCode.X_42501,
sb.toString() + e.getColumnName());
}
else {
assert(obj instanceof ExpressionAggregate);
throw Error.error(ErrorCode.X_47000);
}
}
}
@Override
public OrderedHashSet getUnkeyedColumns(OrderedHashSet unresolvedSet) {
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] == null) {
continue;
}
unresolvedSet = nodes[i].getUnkeyedColumns(unresolvedSet);
}
if (opType == OpTypes.COLUMN
&& !rangeVariable.hasKeyedColumnInGroupBy) {
if (unresolvedSet == null) {
unresolvedSet = new OrderedHashSet();
}
unresolvedSet.add(this);
}
return unresolvedSet;
}
/**
* collects all range variables in expression tree
*/
@Override
void collectRangeVariables(RangeVariable[] rangeVariables, Set set) {
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] != null) {
nodes[i].collectRangeVariables(rangeVariables, set);
}
}
if (rangeVariable != null) {
for (int i = 0; i < rangeVariables.length; i++) {
if (rangeVariables[i] == rangeVariable) {
set.add(rangeVariables[i]);
}
}
}
}
/*
* This class holds the name of an expression to which a column
* reference in an order by resolves. This is actually for error messages, not debugging.
* The indices are useful only if tableName != null.
*
* The T.C case occurs when one references a select list
* element T.C in a column reference. For example:
*
* select T.C, T.C from T where T.C > 0;
*
* In this case, T.C is repeated twice, but it's always the same column in the
* same table. So, the indices, which is to say the order in the select list,
* would be irrelevant. That's why the compareTo method only cares about
* names if there is a table name. If we see this SQL:
*
* select C, C from T where C > 0;
*
* We know C is not an alias, and so we look up C first, and then we
* look up C's table name. So we will be in the non-null table name case.
*
* If there is not a table name, that is to say if tableName == null,
* then we are looking at an alias. This would be the case for this SQL:
*
* select T.C as CC, T.E, T.D as CC from T where CC > 0;
*
* In this case the aliases CC are equal and we care about the indices.
* We want to give an error message which says
* "CC occurs in columns: CC(1), CC(3)".
* The numbers in parentheses are the indices, and the order in which
* the alias occurs in the select list. Note that in this case,
* with two select list expressions aliased as CC, the name CC is not
* resolvable anywhere.
*/
private static class SelectListAliasResolution implements Comparable<SelectListAliasResolution> {
// This is the expression in the select list.
private final Expression m_expression;
// The table alias from the range variable. This is different
// from the table name in queries like "select .. from T as A..."
// where A is the alias and T is the name.
private final String m_tableAlias;
// The column name. This may be null for a general expression.
private final String m_columnName;
// The alias of the column from the select list. If there
// is no alias, this is the column name.
private final String m_alias;
// The zero-based index of the column. This is the order
// of this column in the select list.
//
// This is mostly used to disambiguate if the name is
// not equal.
private final int m_index;
public SelectListAliasResolution(Expression expr, String selectTableAlias,
String selectColumnName, String alias, int index) {
m_expression = expr;
m_tableAlias = selectTableAlias;
m_columnName = selectColumnName;
// Even if m_columnName is null, m_alias will be non-null.
// This can happen in statements like:
// SELECT T.X * R.Y Z AS PROD FROM T, R ORDER BY Z
// where there is no canonical table name or column name
// for Z.
assert alias != null;
m_alias = alias;
m_index = index;
}
public final ExpressionColumn getExpressionColumn() {
if (m_expression instanceof ExpressionColumn) {
return (ExpressionColumn)m_expression;
}
return null;
}
@Override
public String toString() {
if (!m_alias.equals(m_columnName)) {
return m_alias + "(" + m_index + ")";
}
if (m_tableAlias == null) {
return m_alias;
}
return m_tableAlias + "." + m_columnName;
}
// Note: not used by TreeSet. @See compareTo
@Override
public boolean equals(Object other) {
if (other == null) {
return false;
}
if (!(other instanceof SelectListAliasResolution)) {
return false;
}
// The real equals test is here. We defer to compareTo.
SelectListAliasResolution aliasOther = (SelectListAliasResolution)other;
int nc = compareTo(aliasOther);
return nc == 0;
}
/*
* We are comparing two select list elements.
* o The simple case is that they are neither column references.
* This can happen if they are more general expressions, such
* as "(x + 1) as a" compared to a column reference a.
* The m_expressionColumn will be null in the general expression
* case. In this case we just compare the index. Mostly
* these will not be equal, but we need a reliable order.
* o The more complicated case is if they are both column references.
* In this case, m_expressionColumn will be non-null, and this
* ExpressionColumn should have a range variable. We mostly
* ignore any column aliases, since we want to know if these
* two column references are to the same column. So we compare
* table aliases and column names, and we look up the column
* name in the using list. These will be in the range variable.
* Note that we don't care about aliases at all here.
*/
@Override
public int compareTo(SelectListAliasResolution o) {
ExpressionColumn ecol = getExpressionColumn();
ExpressionColumn oecol = o.getExpressionColumn();
if (ecol == null || oecol == null) {
// We only want to be equal if the other's index is
// equal to ours. This will always be false, I think.
return m_index - o.m_index;
}
// They are both column references. We want to return
// 0 if they refer to the same table expression column.
// This is to say, if they have the same column name and
// the same table alias or else their column name is in
// the using list for their range variables. It has to
// be in the using list for both range variables. In
// a query like this:
// select C from R as LR join R as CR using(C), R as RR order by C;
// C is in the using list of the first join (LR and CR) but
// not the third (RR). So, this would be ambiguous.
int nc = m_columnName.compareTo(o.m_columnName);
if (nc != 0) {
return nc;
}
RangeVariable rv = ecol.getRangeVariable();
RangeVariable orv = oecol.getRangeVariable();
if (rv == null) {
if (orv == null) {
return m_index - o.m_index;
}
// Find out if this column name is in the
// using list of orv. If it is, these both
// denote the same column. We want to use the
// alias here because we always have an alias
// and we may not have a column name.
if (orv.getColumnExpression(m_alias) != null) {
return 0;
}
return m_index - o.m_index;
}
if (orv == null) {
// If orv == null but the column names are equal, it
// could be that orv is an instance of a using variable
// and rv is the range variable for the column with
// the same column name. Remember, getColumnExpression()
// just gets ExpressionColumns for USING column names.
if (rv.getColumnExpression(m_columnName) != null) {
return 0;
}
return m_index - o.m_index;
}
ExpressionColumn myEC = rv.getColumnExpression(m_columnName);
ExpressionColumn oEC = orv.getColumnExpression(m_columnName);
if (myEC != null && oEC != null && myEC == oEC) {
// They are the same in a using list.
return 0;
}
assert(m_tableAlias != null);
return m_tableAlias.compareTo(o.m_tableAlias);
}
}
/*
* If we see an ExpressionColumn in an order by expression,
* we we look to see if it's an alias for something in the
* select list. If it is, we replace the column expression
* with the expression to which the alias refers. Note that
* this creates a kind of scoping. If a column reference
* name matches an alias or an unaliased column name in a
* select list it has replaced, and is not ambiguous with
* any other column in the table expression.
*
* For example,
* select r1.b, b from r1 order by b;
* -- b is not ambiguous. Both select list possibilities,
* -- lr.b and b, are to the same table.
*
* select lr.b from r1 lr, r1 rr order by b;
* -- b is not ambiguous. The b in the order by is
* -- replaced by lr.b from the select list.
*
* select lr.b b, rr.b b from r1 lr, r2 rr order by b;
* -- b is ambiguous. There are two aliases named b.
* select lr.b b, b from r1 lr join r1 rr using (b) order by b;
* -- Mysql and postgresql differ in this case. It may
* -- be because of different rules for using.
* -- This is ambiguous with postgresql, but not
* -- with mysql. A close reading of the standard might
* -- suggest that the first reference to lr.b is illegal,
* -- since the USING removes column "b" from the table expression
* -- columns, so perhaps both are wrong. If we ignore that,
* -- it makes sense that this is unambiguous, since the
* -- "b" in the order by, the "b" in the select list and
* -- the "b" in "lr.b" are all references of the same
* -- column.
* select lr.b b, rr.b b from r1 lr join r1 rr using (b) order by b;
* -- It's interesting that this, which is almost exactly the
* same as the previous case, is ambiguous for both mysql and
* postgresql. Apparently rr.b and ll.b are different to the
* mysql SQL compiler, even though the using(b) forces them to
* be identical.
*
* This function calculates the replacement expression. We
* look through the select list, whose expressions are in
* the parameter columns[0:length-1]. This array may have
* some other columns, but these are the only ones we care
* about. For each such Expression, expr, if this column
* reference matches the expression, then put the expression
* in the list of candidates. Since we only want one, we
* will remember the last Expression we added. At the
* end, if there is only one candidate, we return the last
* expression we added. Otherwise we craft an error message
* from the list of candidates. If there are no candidates
* we just return this, and let HSQL return its cryptic not-found
* message.
*
* Consider a variant of the first example above.
* select lr.a, lr.a a, a from r1 lr order by a;
* Here all of these are references to the same table
* expression column. So, these are all unambiguous.
* So, when we add each of these columns to the set,
* of candidates we want only one to be added. We
* need to know when two such expressions refer to the
* same table expression column. We also want to know
* if the column reference from the select list is
* in a using list.
*/
@Override
Expression replaceAliasInOrderBy(Expression[] columns, int length) {
// Recurse into sub-expressions. For example, if
// this expression is e0 + e1, nodes[0] is e0 and
// nodes[1] is e1.
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] == null) {
continue;
}
nodes[i] = nodes[i].replaceAliasInOrderBy(columns, length);
}
// Now process the node itself.
switch (opType) {
case OpTypes.COALESCE :
case OpTypes.COLUMN : {
// Look through all the columns in columns. These are
// the select list, and they may have aliases equal to
// the column which we are trying to resolve. In that
// case we really want to use the expression in the
// select list. Note that we only look for aliases here.
// Column references which name columns in tables in the
// from clause are handled later on.
java.util.Set<SelectListAliasResolution> foundNames = new java.util.TreeSet<>();
Expression firstFoundExpr = null;
for (int i = 0; i < length; i++) {
ExpressionColumn ecol = (columns[i] instanceof ExpressionColumn) ? (ExpressionColumn)columns[i] : null;
// Ferret out the table name, column name
// and alias name from this select column.
// If the alias name is null, then use the column name.
// This may be null as well.
String selectTableName = null;
String selectTableAlias = null;
String selectColumnName = null;
if (ecol != null) {
selectTableName = ecol.getTableName();
selectTableAlias = ecol.getTableAlias();
if (selectTableAlias == null) {
selectTableAlias = selectTableName;
}
selectColumnName = ecol.columnName;
}
SimpleName selectAliasName = columns[i].alias;
String selectAlias = selectAliasName == null ? null : selectAliasName.name;
if (selectAlias == null) {
selectAlias = selectColumnName;
}
// For VoltDB, schema will always be null.
if (schema == null) {
// If this reference has no table name, then
// just compare the aliases. If this reference
// has a table name this is handled
// by the usual lookup rules.
if (tableName == null) {
if (columnName.equals(selectAlias)) {
foundNames.add(new SelectListAliasResolution(columns[i],
selectTableAlias,
selectColumnName,
selectAlias,
i));
if (firstFoundExpr == null) {
firstFoundExpr = columns[i];
}
}
}
}
}
// If we only got one answer, then we just return it.
// If we got more than one, then we print an ambiguous
// error message. If we got no answer, we let HSQL
// handle it in the usual way.
if (foundNames.size() == 1) {
return firstFoundExpr;
}
if (foundNames.size() > 1) {
StringBuffer sb = new StringBuffer();
sb.append(String.format("The name \"%s\" in an order by expression is ambiguous. It's in columns: ", columnName));
appendNameList(sb, foundNames, "");
sb.append(".");
throw new HsqlException(sb.toString(), "", 0);
}
}
default :
}
return this;
}
private String getTableAlias() {
if (getRangeVariable() == null
|| getRangeVariable().tableAlias == null) {
return null;
}
return getRangeVariable().tableAlias.name;
}
@Override
Expression replaceColumnReferences(RangeVariable range,
Expression[] list) {
if (opType == OpTypes.COLUMN && rangeVariable == range) {
return list[columnIndex];
}
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] == null) {
continue;
}
nodes[i] = nodes[i].replaceColumnReferences(range, list);
}
return this;
}
@Override
int findMatchingRangeVariableIndex(RangeVariable[] rangeVarArray) {
for (int i = 0; i < rangeVarArray.length; i++) {
RangeVariable rangeVar = rangeVarArray[i];
if (rangeVar.resolvesTableName(this)) {
return i;
}
}
return -1;
}
/**
* return true if given RangeVariable is used in expression tree
*/
@Override
boolean hasReference(RangeVariable range) {
if (range == rangeVariable) {
return true;
}
for (int i = 0; i < nodes.length; i++) {
if (nodes[i] != null) {
if (nodes[i].hasReference(range)) {
return true;
}
}
}
return false;
}
@Override
public boolean equals(Expression other) {
if (other == this) {
return true;
}
if (other == null) {
return false;
}
if (opType != other.opType) {
return false;
}
switch (opType) {
case OpTypes.SIMPLE_COLUMN :
return columnIndex == other.columnIndex;
case OpTypes.COALESCE :
return nodes == other.nodes;
case OpTypes.COLUMN :
return (other instanceof ExpressionColumn) &&
rangeVariable == ((ExpressionColumn)other).rangeVariable &&
column == ((ExpressionColumn)other).column;
// A VoltDB extension
case OpTypes.ASTERISK :
return true;
// End of VoltDB extension
default :
return false;
}
}
/************************* Volt DB Extensions *************************/
/**
* VoltDB added method to provide detail for a non-catalog-dependent
* representation of this HSQLDB object.
* @return XML, correctly indented, representing this object.
*/
VoltXMLElement voltAnnotateColumnXML(VoltXMLElement exp) {
if (tableName != null) {
if (rangeVariable != null && rangeVariable.rangeTable != null &&
rangeVariable.tableAlias != null &&
rangeVariable.rangeTable.tableType == TableBase.SYSTEM_SUBQUERY) {
exp.attributes.put("table", rangeVariable.tableAlias.name.toUpperCase());
}
else {
exp.attributes.put("table", tableName.toUpperCase());
}
}
exp.attributes.put("column", columnName.toUpperCase());
if ((alias == null) || (getAlias().length() == 0)) {
exp.attributes.put("alias", columnName.toUpperCase());
}
if (rangeVariable != null && rangeVariable.tableAlias != null) {
exp.attributes.put("tablealias", rangeVariable.tableAlias.name.toUpperCase());
}
exp.attributes.put("index", Integer.toString(columnIndex));
return exp;
}
/**********************************************************************/
}