/* 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.plannodes;
import org.json_voltpatches.JSONException;
import org.json_voltpatches.JSONObject;
import org.json_voltpatches.JSONStringer;
import org.voltdb.VoltType;
import org.voltdb.expressions.AbstractExpression;
import org.voltdb.expressions.TupleValueExpression;
/**
* This class encapsulates the data and operations needed to track columns
* in the planner.
*
*/
public class SchemaColumn {
private static class Members {
static final String COLUMN_NAME = "COLUMN_NAME";
static final String EXPRESSION = "EXPRESSION";
}
private String m_tableName;
private String m_tableAlias;
private String m_columnName;
private String m_columnAlias;
private AbstractExpression m_expression;
private int m_differentiator = -1;
/**
* Create a new SchemaColumn
* @param tableName The name of the table where this column originated,
* if any. Currently, internally created columns will be assigned
* the table name AbstractParsedStmt.TEMP_TABLE_NAME for disambiguation.
* @param tableAlias The alias assigned to this table, if any
* @param columnName The name of this column, if any
* @param columnAlias The alias assigned to this column, if any
* @param expression The input expression which generates this
* column. SchemaColumn needs to have exclusive ownership
* so that it can adjust the index of any TupleValueExpressions
* without affecting other nodes/columns/plan iterations, so
* it clones this expression.
*
* Some callers seem to provide an empty string instead of a null. We change the
* empty string to null to simplify the comparison functions.
*/
SchemaColumn(String tableName, String tableAlias,
String columnName, String columnAlias) {
m_tableName = tableName == null || tableName.equals("") ? null : tableName;
m_tableAlias = tableAlias == null || tableAlias.equals("") ? null : tableAlias;
m_columnName = columnName == null || columnName.equals("") ? null : columnName;
m_columnAlias = columnAlias == null || columnAlias.equals("") ? null : columnAlias;
}
public SchemaColumn(String tableName, String tableAlias,
String columnName, String columnAlias,
AbstractExpression expression) {
this(tableName, tableAlias, columnName, columnAlias);
if (expression != null) {
m_expression = expression.clone();
}
}
public SchemaColumn(String tableName, String tableAlias,
String columnName, String columnAlias,
AbstractExpression expression, int differentiator) {
this(tableName, tableAlias, columnName, columnAlias, expression);
m_differentiator = differentiator;
}
/**
* Clone a schema column
*/
@Override
protected SchemaColumn clone() {
return new SchemaColumn(m_tableName, m_tableAlias,
m_columnName, m_columnAlias,
m_expression, m_differentiator);
}
/**
* Determine if this object is equal to another based on a comparison
* of the table and column names or aliases. See compareNames below
* for details.
*/
@Override
public boolean equals (Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (obj instanceof SchemaColumn == false) {
return false;
}
SchemaColumn sc = (SchemaColumn) obj;
if (compareNames(sc) != 0) {
return false;
}
return getDifferentiator() == sc.getDifferentiator();
}
private int nullSafeStringCompareTo(String str1, String str2) {
if (str1 == null ^ str2 == null) {
return str1 == null ? -1 : 1;
}
if (str1 == null && str2 == null) {
return 0;
}
return str1.compareTo(str2);
}
/**
* Compare this schema column to the input.
*
* Two SchemaColumns are compared thus:
*
* - Compare the table aliases or names, preferring to compare aliases if
* not null for both sides.
* - Compare the column names or aliases, preferring to compare names if
* not null for both sides.
*/
public int compareNames(SchemaColumn that) {
String thatTbl;
String thisTbl;
if (m_tableAlias != null && that.m_tableAlias != null) {
thisTbl = m_tableAlias;
thatTbl = that.m_tableAlias;
}
else {
thisTbl = m_tableName;
thatTbl = that.m_tableName;
}
int tblCmp = nullSafeStringCompareTo(thisTbl, thatTbl);
if (tblCmp != 0) {
return tblCmp;
}
String thisCol;
String thatCol;
if (m_columnName != null && that.m_columnName != null) {
thisCol = m_columnName;
thatCol = that.m_columnName;
}
else {
thisCol = m_columnAlias;
thatCol = that.m_columnAlias;
}
int colCmp = nullSafeStringCompareTo(thisCol, thatCol);
return colCmp;
}
@Override
public int hashCode () {
// based on implementation of equals
int result = m_tableAlias != null ?
m_tableAlias.hashCode() :
m_tableName.hashCode();
if (m_columnName != null && !m_columnName.equals("")) {
result += m_columnName.hashCode();
}
else if (m_columnAlias != null && !m_columnAlias.equals("")) {
result += m_columnAlias.hashCode();
}
result += m_differentiator;
return result;
}
/**
* Return a copy of this SchemaColumn, but with the input expression
* replaced by an appropriate TupleValueExpression.
* @param colIndex
*/
public SchemaColumn copyAndReplaceWithTVE(int colIndex) {
TupleValueExpression newTve;
if (m_expression instanceof TupleValueExpression) {
newTve = (TupleValueExpression) m_expression.clone();
newTve.setColumnIndex(colIndex);
}
else {
newTve = new TupleValueExpression(m_tableName, m_tableAlias,
m_columnName, m_columnAlias,
m_expression, colIndex);
}
return new SchemaColumn(m_tableName, m_tableAlias,
m_columnName, m_columnAlias,
newTve, m_differentiator);
}
public String getTableName() { return m_tableName; }
public String getTableAlias() { return m_tableAlias; }
public String getColumnName() { return m_columnName; }
public String getColumnAlias() { return m_columnAlias; }
public void reset(String tbName, String tbAlias,
String colName, String colAlias) {
m_tableName = tbName;
m_tableAlias = tbAlias;
m_columnName = colName;
m_columnAlias = colAlias;
if (m_expression instanceof TupleValueExpression) {
TupleValueExpression tve = (TupleValueExpression) m_expression;
tve.setTableName(m_tableName);
tve.setTableAlias(m_tableAlias);
tve.setColumnName(m_columnName);
tve.setColumnAlias(m_columnAlias);
}
}
public AbstractExpression getExpression() { return m_expression; }
public VoltType getType() { return m_expression.getValueType(); }
public int getSize() { return m_expression.getValueSize(); }
/**
* Return the differentiator that can distinguish columns with the same name.
* This value is just the ordinal position of the SchemaColumn within its NodeSchema.
* @return differentiator for this schema column
*/
public int getDifferentiator() { return m_differentiator; }
/**
* Set the differentiator value for this SchemaColumn.
* @param differentiator
*/
public void setDifferentiator(int differentiator) {
m_differentiator = differentiator;
}
public void toJSONString(JSONStringer stringer, boolean finalOutput)
throws JSONException {
stringer.object();
// Tell the EE that the column name is either a valid column
// alias or the original column name if no alias exists. This is a
// bit hacky, but it's the easiest way for the EE to generate
// a result set that has all the aliases that may have been specified
// by the user (thanks to chains of setOutputTable(getInputTable))
if (finalOutput) {
String columnName = getColumnAlias();
if (columnName == null || columnName.equals("")) {
columnName = getColumnName();
}
stringer.keySymbolValuePair(Members.COLUMN_NAME, columnName);
}
if (m_expression != null) {
stringer.key(Members.EXPRESSION).object();
m_expression.toJSONString(stringer);
stringer.endObject();
}
stringer.endObject();
}
public static SchemaColumn fromJSONObject(JSONObject jobj)
throws JSONException {
String tableName = null;
String tableAlias = null;
String columnName = null;
String columnAlias = null;
AbstractExpression expression = null;
if ( ! jobj.isNull(Members.COLUMN_NAME)){
columnName = jobj.getString(Members.COLUMN_NAME);
}
expression = AbstractExpression.fromJSONChild(jobj, Members.EXPRESSION);
return new SchemaColumn(tableName, tableAlias,
columnName, columnAlias, expression);
}
/**
* Generates a string that can appear in "explain plan" output
* when detailed debug output is enabled.
* @return a string that represents this SchemaColumn
*/
@Override
public String toString() {
String str = "";
String table = getTableAlias();
if (table == null) {
table = getTableName();
}
if (table == null) {
table = "<null>";
}
str += table;
str += ".";
if (getColumnName() != null) {
str += getColumnName();
}
else if (getColumnAlias() != null) {
str += getColumnAlias();
}
else {
str += "<null>";
}
if (m_expression != null) {
str += " expr: (";
if (m_expression.getValueType() != null) {
str += "[" + m_expression.getValueType().toSQLString() + "] ";
}
else {
str += "[!!! value type:NULL !!!] ";
}
str += m_expression.explain(table) + ")";
if (m_expression instanceof TupleValueExpression) {
int tveIndex = ((TupleValueExpression)m_expression).getColumnIndex();
str += " index: " + tveIndex;
}
}
return str;
}
}