--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.compile.OrderByColumn\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.sql.compile;\r
+\r
+import org.apache.derby.iapi.types.TypeId;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.sql.compile.NodeFactory;\r
+import org.apache.derby.iapi.sql.compile.C_NodeTypes;\r
+\r
+import org.apache.derby.iapi.util.ReuseFactory;\r
+\r
+/**\r
+ * An OrderByColumn is a column in the ORDER BY clause. An OrderByColumn\r
+ * can be ordered ascending or descending.\r
+ *\r
+ * We need to make sure that the named columns are\r
+ * columns in that query, and that positions are within range.\r
+ *\r
+ */\r
+public class OrderByColumn extends OrderedColumn {\r
+\r
+ private ResultColumn resultCol;\r
+ private boolean ascending = true;\r
+ private ValueNode expression;\r
+ private OrderByList list;\r
+ /**\r
+ * If this sort key is added to the result column list then it is at result column position\r
+ * 1 + resultColumnList.size() - resultColumnList.getOrderBySelect() + addedColumnOffset\r
+ * If the sort key is already in the result column list then addedColumnOffset < 0.\r
+ */\r
+ private int addedColumnOffset = -1;\r
+\r
+\r
+ /**\r
+ * Initializer.\r
+ *\r
+ * @param expression Expression of this column\r
+ */\r
+ public void init(Object expression)\r
+ {\r
+ this.expression = (ValueNode)expression;\r
+ }\r
+ \r
+ /**\r
+ * Convert this object to a String. See comments in QueryTreeNode.java\r
+ * for how this should be done for tree printing.\r
+ *\r
+ * @return This object as a String\r
+ */\r
+ public String toString() {\r
+ if (SanityManager.DEBUG) {\r
+ return expression.toString();\r
+ } else {\r
+ return "";\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Mark the column as descending order\r
+ */\r
+ public void setDescending() {\r
+ ascending = false;\r
+ }\r
+\r
+ /**\r
+ * Get the column order. Overrides \r
+ * OrderedColumn.isAscending.\r
+ *\r
+ * @return true if ascending, false if descending\r
+ */\r
+ public boolean isAscending() {\r
+ return ascending;\r
+ }\r
+\r
+ /**\r
+ * Get the underlying ResultColumn.\r
+ *\r
+ * @return The underlying ResultColumn.\r
+ */\r
+ ResultColumn getResultColumn()\r
+ {\r
+ return resultCol;\r
+ }\r
+\r
+ /**\r
+ * Get the underlying expression, skipping over ResultColumns that\r
+ * are marked redundant.\r
+ */\r
+ ValueNode getNonRedundantExpression()\r
+ {\r
+ ResultColumn rc;\r
+ ValueNode value;\r
+ ColumnReference colref = null;\r
+\r
+ for (rc = resultCol; rc.isRedundant(); rc = colref.getSource())\r
+ {\r
+ value = rc.getExpression();\r
+\r
+ if (value instanceof ColumnReference)\r
+ {\r
+ colref = (ColumnReference) value;\r
+ }\r
+ else\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ "value should be a ColumnReference, but is a " +\r
+ value.getClass().getName());\r
+ }\r
+ }\r
+ }\r
+\r
+ return rc.getExpression();\r
+ }\r
+\r
+ /**\r
+ * Bind this column.\r
+ *\r
+ * During binding, we may discover that this order by column was pulled\r
+ * up into the result column list, but is now a duplicate, because the\r
+ * actual result column was expanded into the result column list when "*"\r
+ * expressions were replaced with the list of the table's columns. In such\r
+ * a situation, we will end up calling back to the OrderByList to\r
+ * adjust the addedColumnOffset values of the columns; the "oblist"\r
+ * parameter exists to allow that callback to be performed.\r
+ *\r
+ * @param target The result set being selected from\r
+ * @param oblist OrderByList which contains this column\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ * @exception StandardException Thrown when column not found\r
+ */\r
+ public void bindOrderByColumn(ResultSetNode target, OrderByList oblist)\r
+ throws StandardException \r
+ {\r
+ this.list = oblist;\r
+\r
+ if(expression instanceof ColumnReference){\r
+ \r
+ ColumnReference cr = (ColumnReference) expression;\r
+ \r
+ resultCol = resolveColumnReference(target,\r
+ cr);\r
+ \r
+ columnPosition = resultCol.getColumnPosition();\r
+\r
+ if (addedColumnOffset >= 0 &&\r
+ target instanceof SelectNode &&\r
+ ( (SelectNode)target ).hasDistinct())\r
+ throw StandardException.newException(SQLState.LANG_DISTINCT_ORDER_BY, cr.columnName);\r
+ }else if(isReferedColByNum(expression)){\r
+ \r
+ ResultColumnList targetCols = target.getResultColumns();\r
+ columnPosition = ((Integer)expression.getConstantValueAsObject()).intValue();\r
+ resultCol = targetCols.getOrderByColumn(columnPosition);\r
+\r
+ /* Column is out of range if either a) resultCol is null, OR\r
+ * b) resultCol points to a column that is not visible to the\r
+ * user (i.e. it was generated internally).\r
+ */\r
+ if ((resultCol == null) ||\r
+ (resultCol.getColumnPosition() > targetCols.visibleSize()))\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_COLUMN_OUT_OF_RANGE, \r
+ String.valueOf(columnPosition));\r
+ }\r
+\r
+ }else{\r
+ if( SanityManager.DEBUG)\r
+ SanityManager.ASSERT( addedColumnOffset >= 0,\r
+ "Order by expression was not pulled into the result column list");\r
+ resolveAddedColumn(target);\r
+ if (resultCol == null)\r
+ throw StandardException.newException(SQLState.LANG_UNION_ORDER_BY);\r
+ }\r
+\r
+ // Verify that the column is orderable\r
+ resultCol.verifyOrderable();\r
+ }\r
+\r
+ /**\r
+ * Assuming this OrderByColumn was "pulled" into the received target's\r
+ * ResultColumnList (because it wasn't there to begin with), use\r
+ * this.addedColumnOffset to figure out which of the target's result\r
+ * columns is the one corresponding to "this".\r
+ *\r
+ * The desired position is w.r.t. the original, user-specified result\r
+ * column list--which is what "visibleSize()" gives us. I.e. To get\r
+ * this OrderByColumn's position in target's RCL, first subtract out\r
+ * all columns which were "pulled" into the RCL for GROUP BY or ORDER\r
+ * BY, then add "this.addedColumnOffset". As an example, if the query\r
+ * was:\r
+ *\r
+ * select sum(j) as s from t1 group by i, k order by k, sum(k)\r
+ *\r
+ * then we will internally add columns "K" and "SUM(K)" to the RCL for\r
+ * ORDER BY, *AND* we will add a generated column "I" to the RCL for\r
+ * GROUP BY. Thus we end up with four result columns:\r
+ *\r
+ * (1) (2) (3) (4)\r
+ * select sum(j) as s, K, SUM(K), I from t1 ...\r
+ *\r
+ * So when we get here and we want to find out which column "this"\r
+ * corresponds to, we begin by taking the total number of VISIBLE\r
+ * columns, which is 1 (i.e. 4 total columns minus 1 GROUP BY column\r
+ * minus 2 ORDER BY columns). Then we add this.addedColumnOffset in\r
+ * order to find the target column position. Since addedColumnOffset\r
+ * is 0-based, an addedColumnOffset value of "0" means we want the\r
+ * the first ORDER BY column added to target's RCL, "1" means we want\r
+ * the second ORDER BY column added, etc. So if we assume that\r
+ * this.addedColumnOffset is "1" in this example then we add that\r
+ * to the RCL's "visible size". And finally, we add 1 more to account\r
+ * for fact that addedColumnOffset is 0-based while column positions\r
+ * are 1-based. This gives:\r
+ *\r
+ * position = 1 + 1 + 1 = 3\r
+ *\r
+ * which points to SUM(K) in the RCL. Thus an addedColumnOffset\r
+ * value of "1" resolves to column SUM(K) in target's RCL; similarly,\r
+ * an addedColumnOffset value of "0" resolves to "K". DERBY-3303.\r
+ */\r
+ private void resolveAddedColumn(ResultSetNode target)\r
+ {\r
+ ResultColumnList targetCols = target.getResultColumns();\r
+ columnPosition = targetCols.visibleSize() + addedColumnOffset + 1;\r
+ resultCol = targetCols.getResultColumn( columnPosition);\r
+ }\r
+\r
+ /**\r
+ * Pull up this orderby column if it doesn't appear in the resultset\r
+ *\r
+ * @param target The result set being selected from\r
+ *\r
+ */\r
+ public void pullUpOrderByColumn(ResultSetNode target)\r
+ throws StandardException \r
+ {\r
+ ResultColumnList targetCols = target.getResultColumns();\r
+\r
+ if(expression instanceof ColumnReference){\r
+\r
+ ColumnReference cr = (ColumnReference) expression;\r
+\r
+ resultCol = targetCols.findResultColumnForOrderBy(\r
+ cr.getColumnName(), cr.getTableNameNode());\r
+\r
+ if(resultCol == null){\r
+ resultCol = (ResultColumn) getNodeFactory().getNode(C_NodeTypes.RESULT_COLUMN,\r
+ cr.getColumnName(),\r
+ cr,\r
+ getContextManager());\r
+ targetCols.addResultColumn(resultCol);\r
+ addedColumnOffset = targetCols.getOrderBySelect();\r
+ targetCols.incOrderBySelect();\r
+ }\r
+ \r
+ }else if(!isReferedColByNum(expression)){\r
+ resultCol = (ResultColumn) getNodeFactory().getNode(C_NodeTypes.RESULT_COLUMN,\r
+ null,\r
+ expression,\r
+ getContextManager());\r
+ targetCols.addResultColumn(resultCol);\r
+ addedColumnOffset = targetCols.getOrderBySelect();\r
+ targetCols.incOrderBySelect();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Order by columns now point to the PRN above the node of interest.\r
+ * We need them to point to the RCL under that one. This is useful\r
+ * when combining sorts where we need to reorder the sorting\r
+ * columns.\r
+ */\r
+ void resetToSourceRC()\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (! (resultCol.getExpression() instanceof VirtualColumnNode))\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ "resultCol.getExpression() expected to be instanceof VirtualColumnNode " +\r
+ ", not " + resultCol.getExpression().getClass().getName());\r
+ }\r
+ }\r
+\r
+ resultCol = resultCol.getExpression().getSourceResultColumn();\r
+ }\r
+\r
+ /**\r
+ * Is this OrderByColumn constant, according to the given predicate list?\r
+ * A constant column is one where all the column references it uses are\r
+ * compared equal to constants.\r
+ */\r
+ boolean constantColumn(PredicateList whereClause)\r
+ {\r
+ ValueNode sourceExpr = resultCol.getExpression();\r
+\r
+ return sourceExpr.constantExpression(whereClause);\r
+ }\r
+\r
+ /**\r
+ * Remap all the column references under this OrderByColumn to their\r
+ * expressions.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ void remapColumnReferencesToExpressions() throws StandardException\r
+ {\r
+ resultCol.setExpression(\r
+ resultCol.getExpression().remapColumnReferencesToExpressions());\r
+ }\r
+\r
+ private static boolean isReferedColByNum(ValueNode expression) \r
+ throws StandardException{\r
+ \r
+ if(!expression.isConstantExpression()){\r
+ return false;\r
+ }\r
+ \r
+ return expression.getConstantValueAsObject() instanceof Integer;\r
+ }\r
+\r
+ \r
+ private ResultColumn resolveColumnReference(ResultSetNode target,\r
+ ColumnReference cr)\r
+ throws StandardException{\r
+ \r
+ ResultColumn resultCol = null;\r
+ \r
+ int sourceTableNumber = -1;\r
+ \r
+ //bug 5716 - for db2 compatibility - no qualified names allowed in order by clause when union/union all operator is used \r
+\r
+ if (target instanceof SetOperatorNode && cr.getTableName() != null){\r
+ String fullName = cr.getSQLColumnName();\r
+ throw StandardException.newException(SQLState.LANG_QUALIFIED_COLUMN_NAME_NOT_ALLOWED, fullName);\r
+ }\r
+\r
+ if(cr.getTableNameNode() != null){\r
+ TableName tableNameNode = cr.getTableNameNode();\r
+\r
+ FromTable fromTable = target.getFromTableByName(tableNameNode.getTableName(),\r
+ (tableNameNode.hasSchema() ?\r
+ tableNameNode.getSchemaName():null),\r
+ true);\r
+ if(fromTable == null){\r
+ fromTable = target.getFromTableByName(tableNameNode.getTableName(),\r
+ (tableNameNode.hasSchema() ?\r
+ tableNameNode.getSchemaName():null),\r
+ false);\r
+ if(fromTable == null){\r
+ String fullName = cr.getTableNameNode().toString();\r
+ throw StandardException.newException(SQLState.LANG_EXPOSED_NAME_NOT_FOUND, fullName);\r
+ }\r
+ }\r
+\r
+ /* HACK - if the target is a UnionNode, then we have to\r
+ * have special code to get the sourceTableNumber. This is\r
+ * because of the gyrations we go to with building the RCLs\r
+ * for a UnionNode.\r
+ */\r
+ if (target instanceof SetOperatorNode)\r
+ {\r
+ sourceTableNumber = ((FromTable) target).getTableNumber();\r
+ }\r
+ else\r
+ {\r
+ sourceTableNumber = fromTable.getTableNumber();\r
+ }\r
+ \r
+ }\r
+\r
+ ResultColumnList targetCols = target.getResultColumns();\r
+\r
+ resultCol = targetCols.getOrderByColumnToBind(cr.getColumnName(),\r
+ cr.getTableNameNode(),\r
+ sourceTableNumber,\r
+ this);\r
+ /* Search targetCols before using addedColumnOffset because select list wildcards, '*',\r
+ * are expanded after pullUpOrderByColumn is called. A simple column reference in the\r
+ * order by clause may be found in the user specified select list now even though it was\r
+ * not found when pullUpOrderByColumn was called.\r
+ */\r
+ if( resultCol == null && addedColumnOffset >= 0)\r
+ resolveAddedColumn(target);\r
+ \r
+ if (resultCol == null || resultCol.isNameGenerated()){\r
+ String errString = cr.columnName;\r
+ throw StandardException.newException(SQLState.LANG_ORDER_BY_COLUMN_NOT_FOUND, errString);\r
+ }\r
+\r
+ return resultCol;\r
+\r
+ }\r
+\r
+ /**\r
+ * Reset addedColumnOffset to indicate that column is no longer added\r
+ *\r
+ * An added column is one which was artificially added to the result\r
+ * column list due to its presence in the ORDER BY clause, as opposed to\r
+ * having been explicitly selected by the user. Since * is not expanded\r
+ * until after the ORDER BY columns have been pulled up, we may add a\r
+ * column, then later decide it is a duplicate of an explicitly selected\r
+ * column. In that case, this method is called, and it does the following:\r
+ * - resets addedColumnOffset to -1 to indicate this is not an added col\r
+ * - calls back to the OrderByList to adjust any other added cols\r
+ */\r
+ void clearAddedColumnOffset()\r
+ {\r
+ list.closeGap(addedColumnOffset);\r
+ addedColumnOffset = -1;\r
+ }\r
+ /**\r
+ * Adjust addedColumnOffset to reflect that a column has been removed\r
+ *\r
+ * This routine is called when a previously-added result column has been\r
+ * removed due to being detected as a duplicate. If that added column had\r
+ * a lower offset than our column, we decrement our offset to reflect that\r
+ * we have just been moved down one slot in the result column list.\r
+ *\r
+ * @param gap offset of the column which has just been removed from list\r
+ */\r
+ void collapseAddedColumnGap(int gap)\r
+ {\r
+ if (addedColumnOffset > gap)\r
+ addedColumnOffset--;\r
+ }\r
+}\r