001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.compile.OrderByColumn
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.impl.sql.compile;
023:
024: import org.apache.derby.iapi.types.TypeId;
025:
026: import org.apache.derby.iapi.error.StandardException;
027: import org.apache.derby.iapi.reference.SQLState;
028:
029: import org.apache.derby.iapi.services.sanity.SanityManager;
030:
031: import org.apache.derby.iapi.sql.compile.NodeFactory;
032: import org.apache.derby.iapi.sql.compile.C_NodeTypes;
033:
034: import org.apache.derby.iapi.util.ReuseFactory;
035:
036: /**
037: * An OrderByColumn is a column in the ORDER BY clause. An OrderByColumn
038: * can be ordered ascending or descending.
039: *
040: * We need to make sure that the named columns are
041: * columns in that query, and that positions are within range.
042: *
043: * @author ames
044: */
045: public class OrderByColumn extends OrderedColumn {
046:
047: private ResultColumn resultCol;
048: private boolean ascending = true;
049: private ValueNode expression;
050: /**
051: * If this sort key is added to the result column list then it is at result column position
052: * 1 + resultColumnList.size() - resultColumnList.getOrderBySelect() + addedColumnOffset
053: * If the sort key is already in the result column list then addedColumnOffset < 0.
054: */
055: private int addedColumnOffset = -1;
056:
057: /**
058: * Initializer.
059: *
060: * @param expression Expression of this column
061: */
062: public void init(Object expression) {
063: this .expression = (ValueNode) expression;
064: }
065:
066: /**
067: * Convert this object to a String. See comments in QueryTreeNode.java
068: * for how this should be done for tree printing.
069: *
070: * @return This object as a String
071: */
072: public String toString() {
073: if (SanityManager.DEBUG) {
074: return expression.toString();
075: } else {
076: return "";
077: }
078: }
079:
080: /**
081: * Mark the column as descending order
082: */
083: public void setDescending() {
084: ascending = false;
085: }
086:
087: /**
088: * Get the column order. Overrides
089: * OrderedColumn.isAscending.
090: *
091: * @return true if ascending, false if descending
092: */
093: public boolean isAscending() {
094: return ascending;
095: }
096:
097: /**
098: * Get the underlying ResultColumn.
099: *
100: * @return The underlying ResultColumn.
101: */
102: ResultColumn getResultColumn() {
103: return resultCol;
104: }
105:
106: /**
107: * Get the underlying expression, skipping over ResultColumns that
108: * are marked redundant.
109: */
110: ValueNode getNonRedundantExpression() {
111: ResultColumn rc;
112: ValueNode value;
113: ColumnReference colref = null;
114:
115: for (rc = resultCol; rc.isRedundant(); rc = colref.getSource()) {
116: value = rc.getExpression();
117:
118: if (value instanceof ColumnReference) {
119: colref = (ColumnReference) value;
120: } else {
121: if (SanityManager.DEBUG) {
122: SanityManager
123: .THROWASSERT("value should be a ColumnReference, but is a "
124: + value.getClass().getName());
125: }
126: }
127: }
128:
129: return rc.getExpression();
130: }
131:
132: /**
133: * Bind this column.
134: *
135: * @param target The result set being selected from
136: *
137: * @exception StandardException Thrown on error
138: * @exception StandardException Thrown when column not found
139: */
140: public void bindOrderByColumn(ResultSetNode target)
141: throws StandardException {
142: if (expression instanceof ColumnReference) {
143:
144: ColumnReference cr = (ColumnReference) expression;
145:
146: resultCol = resolveColumnReference(target, cr);
147:
148: columnPosition = resultCol.getColumnPosition();
149:
150: } else if (isReferedColByNum(expression)) {
151:
152: ResultColumnList targetCols = target.getResultColumns();
153: columnPosition = ((Integer) expression
154: .getConstantValueAsObject()).intValue();
155: resultCol = targetCols.getOrderByColumn(columnPosition);
156:
157: if (resultCol == null) {
158: throw StandardException.newException(
159: SQLState.LANG_COLUMN_OUT_OF_RANGE, String
160: .valueOf(columnPosition));
161: }
162:
163: } else {
164: if (SanityManager.DEBUG)
165: SanityManager
166: .ASSERT(addedColumnOffset >= 0,
167: "Order by expression was not pulled into the result column list");
168: resolveAddedColumn(target);
169: }
170:
171: // Verify that the column is orderable
172: resultCol.verifyOrderable();
173: }
174:
175: private void resolveAddedColumn(ResultSetNode target) {
176: ResultColumnList targetCols = target.getResultColumns();
177: columnPosition = targetCols.size()
178: - targetCols.getOrderBySelect() + addedColumnOffset + 1;
179: resultCol = targetCols.getResultColumn(columnPosition);
180: }
181:
182: /**
183: * Pull up this orderby column if it doesn't appear in the resultset
184: *
185: * @param target The result set being selected from
186: *
187: */
188: public void pullUpOrderByColumn(ResultSetNode target)
189: throws StandardException {
190: ResultColumnList targetCols = target.getResultColumns();
191:
192: // If the target is generated for a select node then we must also pull the order by column
193: // into the select list of the subquery.
194: if ((target instanceof SelectNode)
195: && ((SelectNode) target).getGeneratedForGroupbyClause()) {
196: if (SanityManager.DEBUG)
197: SanityManager
198: .ASSERT(
199: target.getFromList().size() == 1
200: && (target.getFromList()
201: .elementAt(0) instanceof FromSubquery)
202: && targetCols.size() == 1
203: && targetCols
204: .getResultColumn(1) instanceof AllResultColumn,
205: "Unexpected structure of selectNode generated for a group by clause");
206:
207: ResultSetNode subquery = ((FromSubquery) target
208: .getFromList().elementAt(0)).getSubquery();
209: pullUpOrderByColumn(subquery);
210: if (resultCol == null) // The order by column is referenced by number
211: return;
212:
213: // ResultCol is in the subquery's ResultColumnList. We have to transform this OrderByColumn
214: // so that it refers to the column added to the subquery. We assume that the select list
215: // in the top level target is a (generated) AllResultColumn node, so the this order by expression
216: // does not have to be pulled into the the top level ResultColumnList. Just change this
217: // OrderByColumn to be a reference to the added column. We cannot use an integer column
218: // number because the subquery can have a '*' in its select list, causing the column
219: // number to change when the '*' is expanded.
220: resultCol = null;
221: targetCols.copyOrderBySelect(subquery.getResultColumns());
222: return;
223: }
224:
225: if (expression instanceof ColumnReference) {
226:
227: ColumnReference cr = (ColumnReference) expression;
228:
229: resultCol = targetCols.getOrderByColumn(cr.getColumnName(),
230: cr.getTableNameNode());
231:
232: if (resultCol == null) {
233: resultCol = (ResultColumn) getNodeFactory().getNode(
234: C_NodeTypes.RESULT_COLUMN, cr.getColumnName(),
235: cr, getContextManager());
236: targetCols.addResultColumn(resultCol);
237: addedColumnOffset = targetCols.getOrderBySelect();
238: targetCols.incOrderBySelect();
239: }
240:
241: } else if (!isReferedColByNum(expression)) {
242: resultCol = (ResultColumn) getNodeFactory().getNode(
243: C_NodeTypes.RESULT_COLUMN, null, expression,
244: getContextManager());
245: targetCols.addResultColumn(resultCol);
246: addedColumnOffset = targetCols.getOrderBySelect();
247: targetCols.incOrderBySelect();
248: }
249: }
250:
251: /**
252: * Order by columns now point to the PRN above the node of interest.
253: * We need them to point to the RCL under that one. This is useful
254: * when combining sorts where we need to reorder the sorting
255: * columns.
256: */
257: void resetToSourceRC() {
258: if (SanityManager.DEBUG) {
259: if (!(resultCol.getExpression() instanceof VirtualColumnNode)) {
260: SanityManager
261: .THROWASSERT("resultCol.getExpression() expected to be instanceof VirtualColumnNode "
262: + ", not "
263: + resultCol.getExpression().getClass()
264: .getName());
265: }
266: }
267:
268: VirtualColumnNode vcn = (VirtualColumnNode) resultCol
269: .getExpression();
270: resultCol = vcn.getSourceResultColumn();
271: }
272:
273: /**
274: * Is this OrderByColumn constant, according to the given predicate list?
275: * A constant column is one where all the column references it uses are
276: * compared equal to constants.
277: */
278: boolean constantColumn(PredicateList whereClause) {
279: ValueNode sourceExpr = resultCol.getExpression();
280:
281: return sourceExpr.constantExpression(whereClause);
282: }
283:
284: /**
285: * Remap all the column references under this OrderByColumn to their
286: * expressions.
287: *
288: * @exception StandardException Thrown on error
289: */
290: void remapColumnReferencesToExpressions() throws StandardException {
291: resultCol.setExpression(resultCol.getExpression()
292: .remapColumnReferencesToExpressions());
293: }
294:
295: private static boolean isReferedColByNum(ValueNode expression)
296: throws StandardException {
297:
298: if (!expression.isConstantExpression()) {
299: return false;
300: }
301:
302: return expression.getConstantValueAsObject() instanceof Integer;
303: }
304:
305: private ResultColumn resolveColumnReference(ResultSetNode target,
306: ColumnReference cr) throws StandardException {
307:
308: ResultColumn resultCol = null;
309:
310: int sourceTableNumber = -1;
311:
312: //bug 5716 - for db2 compatibility - no qualified names allowed in order by clause when union/union all operator is used
313:
314: if (target instanceof SetOperatorNode
315: && cr.getTableName() != null) {
316: String fullName = cr.getSQLColumnName();
317: throw StandardException.newException(
318: SQLState.LANG_QUALIFIED_COLUMN_NAME_NOT_ALLOWED,
319: fullName);
320: }
321:
322: if (cr.getTableNameNode() != null) {
323: TableName tableNameNode = cr.getTableNameNode();
324:
325: FromTable fromTable = target.getFromTableByName(
326: tableNameNode.getTableName(), (tableNameNode
327: .hasSchema() ? tableNameNode
328: .getSchemaName() : null), true);
329: if (fromTable == null) {
330: fromTable = target.getFromTableByName(tableNameNode
331: .getTableName(),
332: (tableNameNode.hasSchema() ? tableNameNode
333: .getSchemaName() : null), false);
334: if (fromTable == null) {
335: String fullName = cr.getTableNameNode().toString();
336: throw StandardException.newException(
337: SQLState.LANG_EXPOSED_NAME_NOT_FOUND,
338: fullName);
339: }
340: }
341:
342: /* HACK - if the target is a UnionNode, then we have to
343: * have special code to get the sourceTableNumber. This is
344: * because of the gyrations we go to with building the RCLs
345: * for a UnionNode.
346: */
347: if (target instanceof SetOperatorNode) {
348: sourceTableNumber = ((FromTable) target)
349: .getTableNumber();
350: } else {
351: sourceTableNumber = fromTable.getTableNumber();
352: }
353:
354: }
355:
356: ResultColumnList targetCols = target.getResultColumns();
357:
358: resultCol = targetCols.getOrderByColumn(cr.getColumnName(), cr
359: .getTableNameNode(), sourceTableNumber);
360: /* Search targetCols before using addedColumnOffset because select list wildcards, '*',
361: * are expanded after pullUpOrderByColumn is called. A simple column reference in the
362: * order by clause may be found in the user specified select list now even though it was
363: * not found when pullUpOrderByColumn was called.
364: */
365: if (resultCol == null && addedColumnOffset >= 0)
366: resolveAddedColumn(target);
367:
368: if (resultCol == null || resultCol.isNameGenerated()) {
369: String errString = cr.columnName;
370: throw StandardException.newException(
371: SQLState.LANG_ORDER_BY_COLUMN_NOT_FOUND, errString);
372: }
373:
374: return resultCol;
375:
376: }
377:
378: }
|