/* 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.planner; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.hsqldb_voltpatches.VoltXMLElement; import org.voltdb.expressions.AbstractExpression; import org.voltdb.expressions.ExpressionUtil; import org.voltdb.expressions.TupleValueExpression; import org.voltdb.plannodes.SchemaColumn; /** * This class represents an instance of a column in a parsed statement. * Note that an instance might refer to a downstream temp table, rather * than a column in a persistent table. * * This class is pretty C-struct-like, with all public members and a * 0-argument constructor. It would be nice at some point to clean this * up. */ public class ParsedColInfo implements Cloneable { /* Schema information: table may be AbstractParsedStmt.TEMP_TABLE_NAME */ public String alias = null; public String columnName = null; public String tableName = null; public String tableAlias = null; /** The expression that this column refers to */ public AbstractExpression expression = null; /** Index of the column in its table. This is used for MV sanity-checking */ public int index = 0; /** A differentiator used to tell apart column references when there are * duplicate column names produced by the expansion of "*" that happens in HSQL.*/ public int differentiator = -1; // // Used in ParsedSelectStmt.m_displayColumns // public boolean orderBy = false; public boolean ascending = true; // Only relevant if orderBy is true. public boolean groupBy = false; // Used in ParsedSelectStmt.m_groupByColumns public boolean groupByInDisplay = false; /** Sometimes the expression in the parsed column needs to be adjusted * for its context. This is the case for AVG aggregates in SELECT statements, * which get transformed into SUM/COUNT. This callback interface allows * clients constructing instances of ParsedColInfo to transform the expression * as needed. */ public interface ExpressionAdjuster { AbstractExpression adjust(AbstractExpression expr); } /** Construct a ParsedColInfo from Volt XML. */ static public ParsedColInfo fromOrderByXml(AbstractParsedStmt parsedStmt, VoltXMLElement orderByXml) { // A generic adjuster that just calls finalizeValueTypes ExpressionAdjuster adjuster = new ExpressionAdjuster() { @Override public AbstractExpression adjust(AbstractExpression expr) { ExpressionUtil.finalizeValueTypes(expr); return expr; } }; return fromOrderByXml(parsedStmt, orderByXml, adjuster); } /** Construct a ParsedColInfo from Volt XML. * Allow caller to specify actions to finalize the parsed expression. */ static public ParsedColInfo fromOrderByXml(AbstractParsedStmt parsedStmt, VoltXMLElement orderByXml, ExpressionAdjuster adjuster) { // make sure everything is kosher assert(orderByXml.name.equalsIgnoreCase("orderby")); // get desc/asc String desc = orderByXml.attributes.get("desc"); boolean descending = (desc != null) && (desc.equalsIgnoreCase("true")); // get the columnref or other expression inside the orderby node VoltXMLElement child = orderByXml.children.get(0); assert(child != null); // create the orderby column ParsedColInfo orderCol = new ParsedColInfo(); orderCol.orderBy = true; orderCol.ascending = !descending; AbstractExpression orderExpr = parsedStmt.parseExpressionTree(child); assert(orderExpr != null); orderCol.expression = adjuster.adjust(orderExpr); // Cases: // child could be columnref, in which case it's either a normal column // or an expression. // The latter could be a case if this column came from a subquery that // was optimized out. // Just make a ParsedColInfo object for it and the planner will do the // right thing later. if (orderExpr instanceof TupleValueExpression) { TupleValueExpression tve = (TupleValueExpression) orderExpr; orderCol.columnName = tve.getColumnName(); orderCol.tableName = tve.getTableName(); orderCol.tableAlias = tve.getTableAlias(); if (orderCol.tableAlias == null) { orderCol.tableAlias = orderCol.tableName; } orderCol.alias = tve.getColumnAlias(); } else { String alias = child.attributes.get("alias"); orderCol.alias = alias; orderCol.tableName = AbstractParsedStmt.TEMP_TABLE_NAME; orderCol.tableAlias = AbstractParsedStmt.TEMP_TABLE_NAME; orderCol.columnName = ""; // Replace its expression to TVE after we build the ExpressionIndexMap if ((child.name.equals("operation") == false) && (child.name.equals("aggregation") == false) && (child.name.equals("win_aggregation") == false) && (child.name.equals("function") == false) && (child.name.equals("rank") == false) && (child.name.equals("value") == false) && (child.name.equals("columnref") == false)) { throw new RuntimeException( "ORDER BY parsed with strange child node type: " + child.name); } } return orderCol; } /** Return this as an instance of SchemaColumn */ public SchemaColumn asSchemaColumn() { return new SchemaColumn(tableName, tableAlias, columnName, alias, expression, differentiator); } @Override public String toString() { return "ParsedColInfo: " + tableName + "(" + tableAlias + ")." + columnName + "(" + alias + ") diff'tor:" + differentiator + " expr:(" + expression + ")"; } @Override public boolean equals (Object obj) { if (obj == null) { return false; } if (obj instanceof ParsedColInfo == false) { return false; } ParsedColInfo col = (ParsedColInfo) obj; if (columnName != null && columnName.equals(col.columnName) && tableName != null && tableName.equals(col.tableName) && tableAlias != null && tableAlias.equals(col.tableAlias) && expression != null && expression.equals(col.expression) ) { return true; } return false; } // Based on implementation on equals(). @Override public int hashCode() { int result = new HashCodeBuilder(17, 31). append(columnName).append(tableName).append(tableAlias). toHashCode(); if (expression != null) { result += expression.hashCode(); } return result; } @Override public ParsedColInfo clone() { ParsedColInfo col = null; try { col = (ParsedColInfo) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } col.expression = expression.clone(); return col; } }