/* 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; import java.util.ArrayList; import org.hsqldb.HSQLInterface.HSQLParseException; import org.hsqldb.HsqlNameManager.HsqlName; import org.hsqldb.HsqlNameManager.SimpleName; import org.hsqldb.ParserDQL.CompileContext; import org.hsqldb.lib.OrderedHashSet; import org.hsqldb.result.Result; import org.hsqldb.result.ResultMetaData; /** * Implementation of Statement for query expressions.<p> * * @author Fred Toussi (fredt@users dot sourceforge.net) * @version 1.9.0 * @since 1.9.0 */ public class StatementQuery extends StatementDMQL { StatementQuery(Session session, QueryExpression queryExpression, CompileContext compileContext) { super(StatementTypes.SELECT_CURSOR, StatementTypes.X_SQL_DATA, session.currentSchema); this.queryExpression = queryExpression; setDatabseObjects(compileContext); checkAccessRights(session); } StatementQuery(Session session, QueryExpression queryExpression, CompileContext compileContext, HsqlName[] targets) { super(StatementTypes.SELECT_SINGLE, StatementTypes.X_SQL_DATA, session.currentSchema); this.queryExpression = queryExpression; setDatabseObjects(compileContext); checkAccessRights(session); } @Override Result getResult(Session session) { Result result = queryExpression.getResult(session, session.getMaxRows()); result.setStatement(this); return result; } @Override public ResultMetaData getResultMetaData() { switch (type) { case StatementTypes.SELECT_CURSOR : return queryExpression.getMetaData(); case StatementTypes.SELECT_SINGLE : return queryExpression.getMetaData(); default : throw Error.runtimeError( ErrorCode.U_S0500, "CompiledStatement.getResultMetaData()"); } } @Override void getTableNamesForRead(OrderedHashSet set) { queryExpression.getBaseTableNames(set); for (SubQuery subquerie : subqueries) { if (subquerie.queryExpression != null) { subquerie.queryExpression.getBaseTableNames(set); } } } @Override void getTableNamesForWrite(OrderedHashSet set) {} private static class Pair<T, U> { protected final T m_first; protected final U m_second; public Pair(T first, U second) { m_first = first; m_second = second; } /** * @return the first */ public T getFirst() { return m_first; } /** * @return the second */ public U getSecond() { return m_second; } /** * Convenience class method for constructing pairs using Java's generic type * inference. */ public static <T extends Comparable<T>, U> Pair<T, U> of(T x, U y) { return new Pair<T, U>(x, y); } } /** * Returns true if the specified exprColumn index is in the list of column indices specified by groupIndex * @return true/false */ boolean isGroupByColumn(QuerySpecification select, int index) { if (!select.isGrouped) { return false; } for (int ii = 0; ii < select.groupIndex.getColumnCount(); ii++) { if (index == select.groupIndex.getColumns()[ii]) { return true; } } return false; } /*************** VOLTDB *********************/ /** * VoltDB added method to get a non-catalog-dependent * representation of this HSQLDB object. * @param session The current Session object may be needed to resolve * some names. * @param indent A string of whitespace to be prepended to every line * in the resulting XML. * @param params The parameters (if any) to this compiled SELECT * statement. These are not available to this object, so they are * hackily passed to this method here. * @return XML, correctly indented, representing this object. * @throws HSQLParseException */ @Override String voltGetXML(Session session, String orig_indent) throws HSQLParseException { QuerySpecification select = (QuerySpecification) queryExpression; try { getResult(session); } catch (HsqlException e) { throw new HSQLParseException(e.getMessage()); } catch (Exception e) { // XXX coward. } StringBuffer sb = new StringBuffer(); String indent = orig_indent + HSQLInterface.XML_INDENT; // select sb.append(orig_indent).append("<select"); if (select.isDistinctSelect) sb.append(" distinct=\"true\""); if (select.isGrouped) sb.append(" grouped=\"true\""); if (select.isAggregated) sb.append(" aggregated=\"true\""); // limit if ((select.sortAndSlice != null) && (select.sortAndSlice.limitCondition != null)) { Expression limitCondition = select.sortAndSlice.limitCondition; if (limitCondition.nodes.length != 2) { throw new HSQLParseException("Parser did not create limit and offset expression for LIMIT."); } try { // read offset. it may be a parameter token. if (limitCondition.nodes[0].isParam() == false) { Integer offset = (Integer)limitCondition.nodes[0].getValue(session); if (offset > 0) { sb.append(" offset=\"" + offset + " \""); } } else { sb.append(" offset_paramid=\"" + limitCondition.nodes[0].getUniqueId() + "\""); } // read limit. it may be a parameter token. if (limitCondition.nodes[1].isParam() == false) { Integer limit = (Integer)limitCondition.nodes[1].getValue(session); sb.append(" limit=\"" + limit + "\""); } else { sb.append(" limit_paramid=\"" + limitCondition.nodes[1].getUniqueId() + "\""); } } catch (HsqlException ex) { // XXX really? ex.printStackTrace(); } } sb.append(">\n"); // columns sb.append(indent + "<columns>\n"); ArrayList<Expression> orderByCols = new ArrayList<Expression>(); ArrayList<Expression> groupByCols = new ArrayList<Expression>(); ArrayList<Expression> displayCols = new ArrayList<Expression>(); ArrayList<Pair<Integer, SimpleName>> aliases = new ArrayList<Pair<Integer, SimpleName>>(); /* * select.exprColumn stores all of the columns needed by HSQL to * calculate the query's result set. It contains more than just the * columns in the output; for example, it contains columns representing * aliases, columns for groups, etc. * * Volt uses multiple collections to organize these columns. * * Observing this loop in a debugger, the following seems true: * * 1. Columns in exprColumns that appear in the output schema, appear in * exprColumns in the same order that they occur in the output schema. * * 2. expr.columnIndex is an index back in to the select.exprColumns * array. This allows multiple exprColumn entries to refer to each * other; for example, an OpType.SIMPLE_COLUMN type storing an alias * will have its columnIndex set to the offset of the expr it aliases. */ for (int i = 0; i < select.exprColumns.length; i++) { final Expression expr = select.exprColumns[i]; if (expr.alias != null) { /* * Remember how aliases relate to columns. Will iterate again later * and mutate the exprColumn entries setting the alias string on the aliased * column entry. */ if (expr instanceof ExpressionColumn) { ExpressionColumn exprColumn = (ExpressionColumn)expr; if (exprColumn.alias != null && exprColumn.columnName == null) { aliases.add(Pair.of(expr.columnIndex, expr.alias)); } } else if (expr.columnIndex > -1) { /* * Only add it to the list of aliases that need to be * propagated to columns if the column index is valid. * ExpressionArithmetic will have an alias but not * necessarily a column index. */ aliases.add(Pair.of(expr.columnIndex, expr.alias)); } } // If the column doesn't refer to another exprColumn entry, set its // column index to itself. If all columns have a valid column index, // it's easier to patch up display column ordering later. if (expr.columnIndex == -1) { expr.columnIndex = i; } if (isGroupByColumn(select, i)) { groupByCols.add(expr); } else if (expr.opType == OpTypes.ORDER_BY) { orderByCols.add(expr); } else if (expr.opType == OpTypes.SIMPLE_COLUMN && expr.isAggregate && expr.alias != null) { // Add aggregate aliases to the display columns to maintain // the output schema column ordering. displayCols.add(expr); } else if (expr.opType == OpTypes.SIMPLE_COLUMN) { // Other simple columns are ignored. If others exist, maybe // volt infers a display column from another column collection? } else { displayCols.add(expr); } } for (Pair<Integer, SimpleName> alias : aliases) { // set the alias data into the expression being aliased. select.exprColumns[alias.getFirst()].alias = alias.getSecond(); } /* * The columns chosen above as display columns aren't always the same * expr objects HSQL would use as display columns - some data were * unified (namely, SIMPLE_COLUMN aliases were pushed into COLUMNS). * * However, the correct output schema ordering was correct in exprColumns. * This order was maintained by adding SIMPLE_COLUMNs to displayCols. * * Now need to serialize the displayCols, serializing the non-simple-columns * corresponding to simple_columns for any simple_columns that woodchucks * could chuck. * * Serialize the display columns in the exprColumn order. */ for (int jj=0; jj < displayCols.size(); ++jj) { Expression expr = displayCols.get(jj); if (expr == null) { continue; } else if (expr.opType == OpTypes.SIMPLE_COLUMN) { // simple columns are not serialized as display columns // but they are place holders for another column // in the output schema. Go find that corresponding column // and serialize it in this place. for (int ii=jj; ii < displayCols.size(); ++ii) { Expression otherCol = displayCols.get(ii); if (otherCol == null) { continue; } else if ((otherCol.opType != OpTypes.SIMPLE_COLUMN) && (otherCol.columnIndex == expr.columnIndex)) { // serialize the column this simple column stands-in for sb.append(otherCol.voltGetXML(session, indent + HSQLInterface.XML_INDENT)).append("\n"); // null-out otherCol to not serialize it twice displayCols.set(ii, null); // quit seeking simple_column's replacement break; } } } else { sb.append(expr.voltGetXML(session, indent + HSQLInterface.XML_INDENT)).append("\n"); } } sb.append(indent + "</columns>\n"); // parameters sb.append(indent + "<parameters>\n"); for (int i = 0; i < parameters.length; i++) { sb.append(indent + HSQLInterface.XML_INDENT + "<parameter index='").append(i).append("'"); ExpressionColumn param = parameters[i]; sb.append(" id='").append(param.getUniqueId()).append("'"); sb.append(" type='").append(Types.getTypeName(param.getDataType().typeCode)).append("'"); sb.append(" />\n"); } sb.append(indent + "</parameters>\n"); // scans sb.append(indent + "<tablescans>\n"); for (RangeVariable rangeVariable : rangeVariables) sb.append(rangeVariable.voltGetXML(session, indent + HSQLInterface.XML_INDENT)).append("\n"); sb.append(indent + "</tablescans>\n"); // conditions if (select.queryCondition != null) { sb.append(indent).append("<querycondition>\n"); sb.append(select.queryCondition.voltGetXML(session, indent + HSQLInterface.XML_INDENT)).append("\n"); sb.append(indent).append("</querycondition>\n"); } else { // look for inner joins expressed on range variables Expression cond = null; for (int rvi=0; rvi < select.rangeVariables.length; ++rvi) { RangeVariable rv = rangeVariables[rvi]; // joins on non-indexed columns for inner join tokens created a range variable // and assigned this expression. if (rv.nonIndexJoinCondition != null) { if (cond != null) { cond = new ExpressionLogical(OpTypes.AND, cond, rv.nonIndexJoinCondition); } else { cond = rv.nonIndexJoinCondition; } } // joins on indexed columns for inner join tokens created a range variable // and assigned an expression and set the flag isJoinIndex. else if (rv.isJoinIndex) { if (rv.indexCondition != null) { if (cond != null) { cond = new ExpressionLogical(OpTypes.AND, cond, rv.indexCondition); } else { cond = rv.indexCondition; } } if (rv.indexEndCondition != null) { if (cond != null) { cond = new ExpressionLogical(OpTypes.AND, cond, rv.indexCondition); } else { cond = rv.indexCondition; } } } } if (cond != null) { sb.append(indent).append("<querycondition>\n"); sb.append(cond.voltGetXML(session, indent + HSQLInterface.XML_INDENT)).append("\n"); sb.append(indent).append("</querycondition>\n"); } } // having if (select.havingCondition != null) { sb.append(indent).append("<havingcondition>\n"); sb.append(select.havingCondition.voltGetXML(session, indent + HSQLInterface.XML_INDENT)).append("\n"); sb.append(indent).append("</havingcondition>\n"); } // groupby if (select.isGrouped) { sb.append(indent + "<groupcolumns>\n"); for (Expression groupByCol : groupByCols) { sb.append(groupByCol.voltGetXML(session, indent + HSQLInterface.XML_INDENT)).append("\n"); } sb.append(indent + "</groupcolumns>\n"); } // orderby if (orderByCols.size() > 0) { sb.append(indent + "<ordercolumns>\n"); for (Expression orderByCol : orderByCols) sb.append(orderByCol.voltGetXML(session, indent + HSQLInterface.XML_INDENT)).append("\n"); sb.append(indent + "</ordercolumns>\n"); } sb.append(orig_indent).append("</select>"); return sb.toString(); } }