| /* |
| Derby - Class org.apache.derby.impl.sql.compile.ResultColumnList |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to you under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| |
| package org.apache.derby.impl.sql.compile; |
| |
| import java.lang.reflect.Modifier; |
| import java.sql.ResultSetMetaData; |
| import java.sql.Types; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import org.apache.derby.catalog.types.DefaultInfoImpl; |
| import org.apache.derby.iapi.error.StandardException; |
| import org.apache.derby.iapi.reference.ClassName; |
| import org.apache.derby.iapi.reference.SQLState; |
| import org.apache.derby.iapi.services.classfile.VMOpcode; |
| import org.apache.derby.iapi.services.compiler.LocalField; |
| import org.apache.derby.iapi.services.compiler.MethodBuilder; |
| import org.apache.derby.iapi.services.context.ContextManager; |
| import org.apache.derby.iapi.services.io.FormatableBitSet; |
| import org.apache.derby.iapi.services.loader.ClassFactory; |
| import org.apache.derby.shared.common.sanity.SanityManager; |
| import org.apache.derby.iapi.sql.ResultColumnDescriptor; |
| import org.apache.derby.iapi.sql.conn.LanguageConnectionContext; |
| import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor; |
| import org.apache.derby.iapi.sql.dictionary.ColumnDescriptorList; |
| import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor; |
| import org.apache.derby.iapi.sql.dictionary.DataDictionary; |
| import org.apache.derby.iapi.sql.dictionary.DefaultDescriptor; |
| import org.apache.derby.iapi.sql.dictionary.TableDescriptor; |
| import org.apache.derby.iapi.sql.execute.ExecPreparedStatement; |
| import org.apache.derby.iapi.sql.execute.ExecRow; |
| import org.apache.derby.iapi.sql.execute.ExecRowBuilder; |
| import org.apache.derby.iapi.store.access.ConglomerateController; |
| import org.apache.derby.iapi.store.access.StoreCostController; |
| import org.apache.derby.iapi.store.access.TransactionController; |
| import org.apache.derby.iapi.types.DataTypeDescriptor; |
| import org.apache.derby.iapi.types.DataValueDescriptor; |
| import org.apache.derby.iapi.types.RowLocation; |
| import org.apache.derby.iapi.types.TypeId; |
| import org.apache.derby.iapi.util.JBitSet; |
| |
| /** |
| * A ResultColumnList is the target list of a SELECT, INSERT, or UPDATE. |
| * |
| * @see ResultColumn |
| */ |
| |
| class ResultColumnList extends QueryTreeNodeVector<ResultColumn> |
| { |
| /* Is this the ResultColumnList for an index row? */ |
| protected boolean indexRow; |
| protected long conglomerateId; |
| |
| int orderBySelect = 0; // the number of result columns pulled up |
| // from ORDERBY list |
| /* |
| * A comment on 'orderBySelect'. When we encounter a SELECT .. ORDER BY |
| * statement, the columns (or expressions) in the ORDER BY clause may |
| * or may not have been explicitly mentioned in the SELECT column list. |
| * If the columns were NOT explicitly mentioned in the SELECT column |
| * list, then the parsing of the ORDER BY clause implicitly generates |
| * them into the result column list, because we'll need to have those |
| * columns present at execution time in order to sort by them. Those |
| * generated columns are added to the *end* of the ResultColumnList, and |
| * we keep track of the *number* of those columns in 'orderBySelect', |
| * so we can tell whether we are looking at a generated column by seeing |
| * whether its position in the ResultColumnList is in the last |
| * 'orderBySelect' number of columns. If the SELECT .. ORDER BY |
| * statement uses the "*" token to select all the columns from a table, |
| * then during ORDER BY parsing we redundantly generate the columns |
| * mentioned in the ORDER BY clause into the ResultColumnlist, but then |
| * later in getOrderByColumnToBind we determine that these are |
| * duplicates and we take them back out again. |
| */ |
| |
| /* |
| ** Is this ResultColumnList for a FromBaseTable for an index |
| ** that is to be updated? |
| */ |
| protected boolean forUpdate; |
| |
| // Is a count mismatch allowed - see set/get methods for details. |
| private boolean countMismatchAllowed; |
| |
| // Number of RCs in this RCL at "init" time, before additional |
| // ones were added internally. |
| private int initialListSize = 0; |
| |
| ResultColumnList(ContextManager cm) { |
| super(ResultColumn.class, cm); |
| } |
| |
| /** |
| * Add a ResultColumn (at this point, ResultColumn or |
| * AllResultColumn) to the list |
| * |
| * @param resultColumn The ResultColumn to add to the list |
| */ |
| |
| void addResultColumn(ResultColumn resultColumn) |
| { |
| /* Vectors are 0-based, ResultColumns are 1-based */ |
| resultColumn.setVirtualColumnId(size() + 1); |
| addElement(resultColumn); |
| } |
| |
| /** |
| * Append a given ResultColumnList to this one, resetting the virtual |
| * column ids in the appended portion. |
| * |
| * @param resultColumns The ResultColumnList to be appended |
| * @param destructiveCopy Whether or not this is a descructive copy |
| * from resultColumns |
| */ |
| void appendResultColumns(ResultColumnList resultColumns, |
| boolean destructiveCopy) |
| { |
| int oldSize = size(); |
| int newID = oldSize + 1; |
| |
| /* |
| ** Set the virtual column ids in the list being appended. |
| ** Vectors are zero-based, and virtual column ids are one-based, |
| ** so the new virtual column ids start at the original size |
| ** of this list, plus one. |
| */ |
| for (ResultColumn rc : resultColumns) |
| { |
| /* ResultColumns are 1-based */ |
| rc.setVirtualColumnId(newID); |
| newID++; |
| } |
| |
| if (destructiveCopy) |
| { |
| destructiveAppend(resultColumns); |
| } |
| else |
| { |
| nondestructiveAppend(resultColumns); |
| } |
| } |
| |
| /** |
| * Get a ResultColumn from a column position (1-based) in the list |
| * |
| * @param position The ResultColumn to get from the list (1-based) |
| * |
| * @return the column at that position. |
| */ |
| |
| ResultColumn getResultColumn(int position) |
| { |
| /* |
| ** First see if it falls in position x. If not, |
| ** search the whole shebang |
| */ |
| if (position <= size()) |
| { |
| // this wraps the cast needed, |
| // and the 0-based nature of the Vectors. |
| ResultColumn rc = elementAt(position-1); |
| if (rc.getColumnPosition() == position) |
| { |
| return rc; |
| } |
| } |
| |
| /* |
| ** Check each column |
| */ |
| int size = size(); |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn rc = elementAt(index); |
| if (rc.getColumnPosition() == position) |
| { |
| return rc; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Take a column position and a ResultSetNode and find the ResultColumn |
| * in this RCL whose source result set is the same as the received |
| * RSN and whose column position is the same as the received column |
| * position. |
| * |
| * @param colNum The column position (w.r.t rsn) for which we're searching |
| * @param rsn The result set node for which we're searching. |
| * @return The ResultColumn in this RCL whose source is column colNum |
| * in result set rsn. That ResultColumn's position w.r.t to this RCL |
| * is also returned via the whichRC parameter. If no match is found, |
| * return null and leave whichRC untouched. |
| */ |
| ResultColumn getResultColumn(int colNum, ResultSetNode rsn, |
| int [] whichRC) throws StandardException |
| { |
| if (colNum == -1) |
| return null; |
| |
| int [] crColNum = new int[] { -1 }; |
| |
| for (int index = size() - 1; index >= 0; index--) |
| { |
| ResultColumn rc = elementAt(index); |
| if (!(rc.getExpression() instanceof ColumnReference)) |
| { |
| // If the rc's expression isn't a column reference then |
| // it can't be pointing to rsn, so just skip it. |
| continue; |
| } |
| |
| ColumnReference colRef = (ColumnReference)rc.getExpression(); |
| if ((rsn == colRef.getSourceResultSet(crColNum)) && |
| (crColNum[0] == colNum)) |
| { |
| // Found a match. |
| whichRC[0] = index+1; |
| return rc; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Get a ResultColumn from a column position (1-based) in the list, |
| * null if out of range (for order by). |
| * |
| * @param position The ResultColumn to get from the list (1-based) |
| * |
| * @return the column at that position, null if out of range |
| */ |
| ResultColumn getOrderByColumn(int position) |
| { |
| // this wraps the cast needed, and the 0-based nature of the Vectors. |
| if (position == 0) |
| return null; |
| |
| return getResultColumn(position); |
| } |
| |
| /** |
| * Get a ResultColumn that matches the specified columnName and |
| * mark the ResultColumn as being referenced. |
| * |
| * @param columnName The ResultColumn to get from the list |
| * |
| * @return the column that matches that name. |
| */ |
| |
| ResultColumn getResultColumn(String columnName) |
| { |
| return getResultColumn( columnName, true ); |
| } |
| |
| /** |
| * Get a ResultColumn that matches the specified columnName. If requested |
| * to, mark the column as referenced. |
| * |
| * @param columnName The ResultColumn to get from the list |
| * @param markIfReferenced True if we should mark this column as referenced. |
| * |
| * @return the column that matches that name. |
| */ |
| |
| ResultColumn getResultColumn(String columnName, boolean markIfReferenced ) |
| { |
| for (ResultColumn resultColumn : this) |
| { |
| if (columnName.equals( resultColumn.getName()) ) |
| { |
| /* Mark ResultColumn as referenced and return it */ |
| if ( markIfReferenced ) { resultColumn.setReferenced(); } |
| return resultColumn; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Return a result column, if any found, which contains in its |
| * expression/{VCN,CR} chain a result column with the given |
| * columnNumber from a FromTable with the given tableNumber. |
| * <p/> |
| * Used by the optimizer preprocess phase when it is flattening queries, |
| * which has to use the pair {table number, column number} to |
| * uniquely distinguish the column desired in situations where the same |
| * table may appear multiple times in the queries with separate correlation |
| * names, and/or column names from different tables may be the same (hence |
| * looking up by column name will not always work), cf DERBY-4679. |
| * <p/> |
| * {@code columnName} is used to assert that we find the right column. |
| * If we found a match on (tn, cn) but columnName is wrong, return null. |
| * Once we trust table numbers and column numbers to always be correct, |
| * cf. DERBY-4695, we could remove this parameter. |
| * |
| * @param tableNumber the table number to look for |
| * @param columnNumber the column number to look for |
| * @param columnName name of the desired column |
| */ |
| public ResultColumn getResultColumn(int tableNumber, |
| int columnNumber, |
| String columnName) |
| { |
| int size = size(); |
| |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn resultColumn = elementAt(index); |
| ResultColumn rc = resultColumn; |
| |
| while (rc != null) { |
| ValueNode exp = rc.getExpression(); |
| |
| if (exp instanceof VirtualColumnNode) { |
| VirtualColumnNode vcn = (VirtualColumnNode)exp; |
| ResultSetNode rsn = vcn.getSourceResultSet(); |
| |
| if (rsn instanceof FromTable) { |
| FromTable ft = (FromTable)rsn; |
| |
| if (ft.getTableNumber() == tableNumber) { |
| // We have the right table, now try to match the |
| // column number. Looking at a join, for a base |
| // table participant, we will find the correct |
| // column position in the |
| // JOIN's ColumnDescriptor. Normally, we could just |
| // call rc.getColumnPosition, but this doesn't work |
| // if we have a join with a subquery participant |
| // (it would give us the virtualColumnId one level |
| // too high up, since the column descriptor is null |
| // in that case inside a JOIN's RC. |
| // |
| // If FromTable is a FromSubquery we need to look |
| // at the JOIN RC's source column to match the |
| // table column number. However, at that level, the |
| // table number would be that of the underlying |
| // SELECT (for example), rather than the |
| // FromSubquery's, so we need to match the table |
| // number one level above, cf the test cases in |
| // JoinTest#testDerby_4679 which have subqueries. |
| |
| ColumnDescriptor cd = rc.getTableColumnDescriptor(); |
| |
| if (SanityManager.DEBUG) { |
| SanityManager.ASSERT( |
| cd != null || ft instanceof FromSubquery); |
| } |
| |
| if ( (cd != null && cd.getPosition() == |
| columnNumber) || |
| (vcn.getSourceColumn().getColumnPosition() == |
| columnNumber) ) { |
| |
| // Found matching (t,c) within this top |
| // resultColumn. Now do sanity check that column |
| // name is correct. Remove when DERBY-4695 is |
| // fixed. |
| if (columnName.equals( |
| vcn.getSourceColumn().getName())) { |
| resultColumn.setReferenced(); |
| return resultColumn; |
| } else { |
| if (SanityManager.DEBUG) { |
| SanityManager.ASSERT( |
| false, |
| "wrong (tn,cn) for column " + |
| columnName + |
| " found: this pair points to " + |
| vcn.getSourceColumn().getName()); |
| } |
| // Fall back on column name based lookup, |
| // cf. DERBY-4679. See ColumnReference# |
| // remapColumnReferencesToExpressions |
| return null; |
| } |
| } else { |
| rc = vcn.getSourceColumn(); |
| } |
| } else { |
| rc = vcn.getSourceColumn(); |
| } |
| } else { |
| rc = null; |
| } |
| } else if (exp instanceof ColumnReference) { |
| ColumnReference cr = (ColumnReference)exp; |
| |
| if (cr.getTableNumber() == tableNumber && |
| cr.getColumnNumber() == columnNumber) { |
| // Found matching (t,c) within this top resultColumn |
| resultColumn.setReferenced(); |
| return resultColumn; |
| } else { |
| rc = null; |
| } |
| } else { |
| if (SanityManager.DEBUG) { |
| SanityManager.ASSERT( |
| exp instanceof BaseColumnNode, |
| "expected BaseColumnNode, found: " + |
| exp.getClass()); |
| } |
| rc = null; |
| } |
| } |
| |
| } |
| return null; |
| } |
| |
| |
| /** |
| * Get a ResultColumn that matches the specified columnName and |
| * mark the ResultColumn as being referenced. |
| * |
| * @param columnsTableName Qualifying name for the column |
| * @param columnName The ResultColumn to get from the list |
| * |
| * @return the column that matches that name. |
| */ |
| |
| ResultColumn getResultColumn(String columnsTableName, String columnName) |
| { |
| int size = size(); |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn resultColumn = elementAt(index); |
| |
| /* If the column's table name is non-null, then we have found a match |
| * only if the RC's table name is non-null and the same as the |
| * the CR's table name. |
| */ |
| if (columnsTableName != null) |
| { |
| if (resultColumn.getTableName() == null) |
| { |
| continue; |
| } |
| |
| if (! columnsTableName.equals(resultColumn.getTableName())) |
| { |
| continue; |
| } |
| } |
| if (columnName.equals( resultColumn.getName()) ) |
| { |
| /* Mark ResultColumn as referenced and return it */ |
| resultColumn.setReferenced(); |
| return resultColumn; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Get a ResultColumn that matches the specified columnName and |
| * mark the ResultColumn as being referenced. |
| * NOTE - this flavor enforces no ambiguity (at most 1 match) |
| * Only FromSubquery needs to call this flavor since |
| * it can have ambiguous references in its own list. |
| * |
| * @param cr The ColumnReference to resolve |
| * @param exposedTableName Exposed table name for FromTable |
| * @param considerGeneratedColumns Also consider columns that are generated. |
| * One example of this is group by where columns are added to the select list |
| * if they are referenced in the group by but are not present in the select |
| * list. |
| * @return the column that matches that name. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| |
| ResultColumn getAtMostOneResultColumn( |
| ColumnReference cr, |
| String exposedTableName, |
| boolean considerGeneratedColumns) |
| throws StandardException |
| { |
| ResultColumn retRC = null; |
| String columnName = cr.getColumnName(); |
| |
| for (ResultColumn resultColumn : this) |
| { |
| if (columnName.equals( resultColumn.getName())) |
| { |
| if (resultColumn.isGenerated() && !considerGeneratedColumns) { |
| continue; |
| } |
| /* We should get at most 1 match */ |
| if (retRC != null) |
| { |
| throw StandardException.newException(SQLState.LANG_AMBIGUOUS_COLUMN_NAME_IN_TABLE, |
| columnName, exposedTableName); |
| } |
| /* Mark ResultColumn as referenced and return it */ |
| resultColumn.setReferenced(); |
| retRC = resultColumn; |
| } |
| } |
| return retRC; |
| } |
| |
| /** |
| * Return true if some columns in this list are updatable. |
| * |
| * @return true if any column in list is updatable, else false |
| */ |
| boolean columnsAreUpdatable() |
| { |
| for (ResultColumn rc : this) |
| { |
| if (rc.isUpdatable()) |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * For order by column bind, get a ResultColumn that matches the specified |
| * columnName. |
| * |
| * This method is called during bind processing, in the special |
| * "bind the order by" call that is made by CursorNode.bindStatement(). |
| * The OrderByList has a special set of bind processing routines |
| * that analyzes the columns in the ORDER BY list and verifies that |
| * each column is one of: |
| * - a direct reference to a column explicitly mentioned in |
| * the SELECT list |
| * - a direct reference to a column implicitly mentioned as "SELECT *" |
| * - a direct reference to a column "pulled up" into the result |
| * column list |
| * - or a valid and fully-bound expression ("c+2", "YEAR(hire_date)", etc.) |
| * |
| * At this point in the processing, it is possible that we'll find |
| * the column present in the RCL twice: once because it was pulled |
| * up during statement compilation, and once because it was added |
| * when "SELECT *" was expanded into the table's actual column list. |
| * If we find such a duplicated column, we can, and do, remove the |
| * pulled-up copy of the column and point the OrderByColumn |
| * to the actual ResultColumn from the *-expansion. |
| * |
| * Note that the association of the OrderByColumn with the |
| * corresponding ResultColumn in the RCL occurs in |
| * OrderByColumn.resolveAddedColumn. |
| * |
| * @param columnName The ResultColumn to get from the list |
| * @param tableName The table name on the OrderByColumn, if any |
| * @param tableNumber The tableNumber corresponding to the FromTable with the |
| * exposed name of tableName, if tableName != null. |
| * @param obc The OrderByColumn we're binding. |
| * |
| * @return the column that matches that name. |
| * @exception StandardException thrown on ambiguity |
| */ |
| ResultColumn getOrderByColumnToBind( |
| String columnName, |
| TableName tableName, |
| int tableNumber, |
| OrderByColumn obc) |
| throws StandardException |
| { |
| int size = size(); |
| ResultColumn retVal = null, resultColumn; |
| |
| for (int index = 0; index < size; index++) |
| { |
| resultColumn = elementAt(index); |
| |
| /* The order by column is qualified, then it is okay to consider |
| * this RC if: |
| * o The RC is qualified and the qualifiers on the order by column |
| * and the RC are equal(). |
| * o The RC is not qualified, but its expression is a ColumnReference |
| * from the same table (as determined by the tableNumbers). |
| */ |
| boolean columnNameMatches; |
| if (tableName != null) |
| { |
| ValueNode rcExpr = resultColumn.getExpression(); |
| if (! (rcExpr instanceof ColumnReference)) |
| continue; |
| |
| ColumnReference cr = (ColumnReference) rcExpr; |
| if( (! tableName.equals( cr.getQualifiedTableName())) && tableNumber != cr.getTableNumber()) |
| { |
| continue; |
| } |
| columnNameMatches = |
| columnName.equals( resultColumn.getSourceColumnName() ); |
| } |
| else |
| { |
| columnNameMatches = |
| resultColumn.columnNameMatches(columnName); |
| } |
| |
| |
| /* We finally got past the qualifiers, now see if the column |
| * names are equal. If they are, then we appear to have found |
| * our order by column. If we find our order by column multiple |
| * times, make sure that they are truly duplicates, otherwise |
| * we have an ambiguous situation. For example, the query |
| * SELECT b+c AS a, d+e AS a FROM t ORDER BY a |
| * is ambiguous because we don't know which "a" is meant. But |
| * SELECT t.a, t.* FROM t ORDER BY a |
| * is not ambiguous, even though column "a" is selected twice. |
| * If we find our ORDER BY column at the end of the |
| * SELECT column list, in the last 'orderBySelect' number |
| * of columns, then this column was not explicitly mentioned |
| * by the user in their SELECT column list, but was implicitly |
| * added by the parsing of the ORDER BY clause, and it |
| * should be removed from the ResultColumnList and returned |
| * to the caller. |
| */ |
| if (columnNameMatches) |
| { |
| if (retVal == null) |
| { |
| retVal = resultColumn; |
| } |
| else if (! retVal.isEquivalent(resultColumn)) |
| { |
| throw StandardException.newException(SQLState.LANG_DUPLICATE_COLUMN_FOR_ORDER_BY, columnName); |
| } |
| else if (index >= size - orderBySelect) |
| {// remove the column due to pullup of orderby item |
| removeElement(resultColumn); |
| decOrderBySelect(); |
| obc.clearAddedColumnOffset(); |
| collapseVirtualColumnIdGap( |
| resultColumn.getColumnPosition()); |
| break; |
| } |
| } |
| } |
| return retVal; |
| } |
| |
| /** |
| * Adjust virtualColumnId values due to result column removal |
| * |
| * This method is called when a duplicate column has been detected and |
| * removed from the list. We iterate through each of the other columns |
| * in the list and notify them of the column removal so they can adjust |
| * their virtual column id if necessary. |
| * |
| * @param gap id of the column which was just removed. |
| */ |
| private void collapseVirtualColumnIdGap(int gap) |
| { |
| for (ResultColumn rc : this) { |
| rc.collapseVirtualColumnIdGap(gap); |
| } |
| } |
| |
| |
| /** |
| * For order by, get a ResultColumn that matches the specified |
| * columnName. |
| * |
| * This method is called during pull-up processing, at the very |
| * start of bind processing, as part of |
| * OrderByList.pullUpOrderByColumns. Its job is to figure out |
| * whether the provided column (from the ORDER BY list) already |
| * exists in the ResultColumnList or not. If the column does |
| * not exist in the RCL, we return NULL, which signifies that |
| * a new ResultColumn should be generated and added ("pulled up") |
| * to the RCL by our caller. |
| * |
| * Note that at this point in the processing, we should never |
| * find this column present in the RCL multiple times; if the |
| * column is already present in the RCL, then we don't need to, |
| * and won't, pull a new ResultColumn up into the RCL. |
| * |
| * If the caller specified "SELECT *", then the RCL at this |
| * point contains a special AllResultColumn object. This object |
| * will later be expanded and replaced by the actual set of |
| * columns in the table, but at this point we don't know what |
| * those columns are, so we may pull up an OrderByColumn |
| * which duplicates a column in the *-expansion; such |
| * duplicates will be removed at the end of bind processing |
| * by OrderByList.bindOrderByColumns. |
| * |
| * @param columnName The ResultColumn to get from the list |
| * @param tableName The table name on the OrderByColumn, if any |
| * |
| * @return the column that matches that name, or NULL if pull-up needed |
| * @exception StandardException thrown on ambiguity |
| */ |
| ResultColumn findResultColumnForOrderBy( |
| String columnName, TableName tableName) |
| throws StandardException |
| { |
| int size = size(); |
| ResultColumn retVal = null, resultColumn; |
| |
| for (int index = 0; index < size; index++) |
| { |
| resultColumn = elementAt(index); |
| |
| // We may be checking on "ORDER BY T.A" against "SELECT *". |
| // exposedName will not be null and "*" will not have an expression |
| // or tablename. |
| // We may be checking on "ORDER BY T.A" against "SELECT T.B, T.A". |
| boolean columnNameMatches; |
| if (tableName != null) |
| { |
| ValueNode rcExpr = resultColumn.getExpression(); |
| if (rcExpr == null || ! (rcExpr instanceof ColumnReference)) |
| { |
| continue; |
| } |
| ColumnReference cr = (ColumnReference) rcExpr; |
| if( ! tableName.equals( cr.getQualifiedTableName())) |
| { |
| continue; |
| } |
| columnNameMatches = |
| columnName.equals( resultColumn.getSourceColumnName() ); |
| } |
| else |
| columnNameMatches = |
| resultColumn.columnNameMatches(columnName); |
| |
| /* We finally got past the qualifiers, now see if the column |
| * names are equal. |
| */ |
| if (columnNameMatches) |
| { |
| if (retVal == null) |
| { |
| retVal = resultColumn; |
| } |
| else if (! retVal.isEquivalent(resultColumn)) |
| { |
| throw StandardException.newException(SQLState.LANG_DUPLICATE_COLUMN_FOR_ORDER_BY, columnName); |
| } |
| else if (index >= size - orderBySelect) |
| { |
| if (SanityManager.DEBUG) |
| SanityManager.THROWASSERT( |
| "Unexpectedly found ORDER BY column '" + |
| columnName + "' pulled up at position " +index); |
| } |
| } |
| } |
| return retVal; |
| } |
| |
| /** |
| * Copy the result column names from the given ResultColumnList |
| * to this ResultColumnList. This is useful for insert-select, |
| * where the columns being inserted into may be different from |
| * the columns being selected from. The result column list for |
| * an insert is supposed to have the column names being inserted |
| * into. |
| * |
| * @param nameList The ResultColumnList from which to copy |
| * the column names |
| */ |
| |
| void copyResultColumnNames(ResultColumnList nameList) |
| { |
| /* List checking is done during bind(). Lists should be the |
| * same size when we are called. |
| */ |
| if (SanityManager.DEBUG) |
| { |
| if ( (! countMismatchAllowed) && |
| visibleSize() != nameList.visibleSize() ) |
| { |
| SanityManager.THROWASSERT( |
| "The size of the 2 lists is expected to be the same. " + |
| "visibleSize() = " + visibleSize() + |
| ", nameList.visibleSize() = " + nameList.visibleSize()); |
| } |
| } |
| |
| int size = |
| (countMismatchAllowed) ? nameList.visibleSize() : visibleSize(); |
| |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn thisResultColumn = elementAt(index); |
| ResultColumn nameListResultColumn = nameList.elementAt(index); |
| |
| thisResultColumn.setName(nameListResultColumn.getName()); |
| thisResultColumn.setNameGenerated(nameListResultColumn.isNameGenerated()); |
| } |
| } |
| |
| |
| /** |
| * Bind the expressions in this ResultColumnList. This means binding |
| * the expression under each ResultColumn node. |
| * |
| * @param fromList The FROM list for the query this |
| * expression is in, for binding columns. |
| * @param subqueryList The subquery list being built as we find SubqueryNodes |
| * @param aggregates The aggregate list being built as we find AggregateNodes |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void bindExpressions( |
| FromList fromList, SubqueryList subqueryList, List<AggregateNode> aggregates) |
| throws StandardException |
| { |
| /* First we expand the *'s in the result column list */ |
| expandAllsAndNameColumns(fromList); |
| |
| /* Now we bind each result column */ |
| int size = size(); |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn vn = elementAt(index); |
| vn = vn.bindExpression(fromList, subqueryList, aggregates); |
| setElementAt(vn, index); |
| } |
| } |
| |
| /** |
| * Bind the result columns to the expressions that live under them. |
| * All this does is copy the datatype information to from each expression |
| * to each result column. This is useful for SELECT statements, where |
| * the result type of each column is the type of the column's expression. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void bindResultColumnsToExpressions() |
| throws StandardException |
| { |
| for (ResultColumn rc : this) |
| { |
| rc.bindResultColumnToExpression(); |
| } |
| } |
| |
| /** |
| * Bind the result columns by their names. This is useful for GRANT and REVOKE statements |
| * like "GRANT SELECT ON t(c1,c1,c3) TO george", where the user specified a column list. |
| * This method does not check for duplicate column names. |
| * |
| * @param targetTableDescriptor The descriptor for the table |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void bindResultColumnsByName(TableDescriptor targetTableDescriptor) |
| throws StandardException |
| { |
| int size = size(); |
| |
| for (int index = 0; index < size; index++) |
| { |
| elementAt(index).bindResultColumnByName( |
| targetTableDescriptor, |
| index + 1 |
| ); |
| } |
| } // end of bindResultColumnsByName( TableDescriptor) |
| |
| /** |
| * Bind the result columns by their names. This is useful for update, grant, and revoke |
| * statements, and for INSERT statements like "insert into t (a, b, c) |
| * values (1, 2, 3)" where the user specified a column list. |
| * If the statment is an insert or update verify that the result column list does not contain any duplicates. |
| * NOTE: We pass the ResultColumns position in the ResultColumnList so |
| * that the VirtualColumnId gets set. |
| * |
| * @param targetTableDescriptor The descriptor for the table being |
| * updated or inserted into |
| * @param statement DMLStatementNode containing this list, null if no duplicate checking is to be done |
| * |
| * @return A FormatableBitSet representing the set of columns with respect to the table |
| * |
| * @exception StandardException Thrown on error |
| */ |
| FormatableBitSet bindResultColumnsByName(TableDescriptor targetTableDescriptor, |
| DMLStatementNode statement) |
| throws StandardException |
| { |
| int size = size(); |
| FormatableBitSet columnBitSet = new FormatableBitSet( targetTableDescriptor.getNumberOfColumns()); |
| |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn rc = elementAt(index); |
| |
| rc.bindResultColumnByName( |
| targetTableDescriptor, |
| index + 1 |
| ); |
| |
| int colIdx = rc.getColumnPosition() - 1; |
| if( SanityManager.DEBUG) |
| SanityManager.ASSERT( colIdx >= 0 && colIdx < targetTableDescriptor.getNumberOfColumns(), |
| "Invalid column position found for " + rc.getName()); |
| /* Verify that this column's name is unique within the list if requested */ |
| if( statement != null && columnBitSet.isSet( colIdx)) |
| { |
| String colName = rc.getName(); |
| |
| if (statement instanceof UpdateNode) |
| { |
| throw StandardException.newException(SQLState.LANG_DUPLICATE_COLUMN_NAME_UPDATE, colName); |
| } |
| else |
| { |
| throw StandardException.newException(SQLState.LANG_DUPLICATE_COLUMN_NAME_INSERT, colName); |
| } |
| } |
| columnBitSet.set( colIdx); |
| } |
| return columnBitSet; |
| } |
| |
| /** |
| * Bind the result columns by their names. This is useful for update |
| * VTI statements, and for INSERT statements like "insert into new t() (a, b, c) |
| * values (1, 2, 3)" where the user specified a column list. |
| * Also, verify that the result column list does not contain any duplicates. |
| * NOTE: We pass the ResultColumns position in the ResultColumnList so |
| * that the VirtualColumnId gets set. |
| * |
| * @param fullRCL The full RCL for the target table |
| * @param statement DMLStatementNode containing this list |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void bindResultColumnsByName(ResultColumnList fullRCL, |
| FromVTI targetVTI, |
| DMLStatementNode statement) |
| throws StandardException |
| { |
| int size = size(); |
| HashSet<String> seenNames = new HashSet<String>(size + 2, 0.999f); |
| |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn matchRC; |
| ResultColumn rc = elementAt(index); |
| |
| /* Verify that this column's name is unique within the list */ |
| String colName = rc.getName(); |
| boolean alreadySeen = !seenNames.add(colName); |
| |
| if (alreadySeen) |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT((statement instanceof UpdateNode) || |
| (statement instanceof InsertNode), |
| "statement is expected to be instanceof UpdateNode or InsertNode"); |
| } |
| if (statement instanceof UpdateNode) |
| { |
| throw StandardException.newException(SQLState.LANG_DUPLICATE_COLUMN_NAME_UPDATE, colName); |
| } |
| else |
| { |
| throw StandardException.newException(SQLState.LANG_DUPLICATE_COLUMN_NAME_INSERT, colName); |
| } |
| } |
| |
| matchRC = fullRCL.getResultColumn(null, rc.getName()); |
| if (matchRC == null) |
| { |
| throw StandardException.newException(SQLState.LANG_COLUMN_NOT_FOUND_IN_TABLE, |
| rc.getName(), |
| targetVTI.getMethodCall().getJavaClassName()); |
| } |
| |
| /* We have a match. We need to create a dummy ColumnDescriptor |
| * since calling code expects one to get column info. |
| */ |
| ColumnDescriptor cd = new ColumnDescriptor( |
| rc.getName(), |
| matchRC.getVirtualColumnId(), |
| matchRC.getType(), |
| null, |
| null, |
| (TableDescriptor) null, |
| null, |
| 0, 0); |
| rc.setColumnDescriptor(null, cd); |
| rc.setVirtualColumnId(index + 1); |
| } |
| } |
| |
| /** |
| * Bind the result columns by ordinal position. This is useful for |
| * INSERT statements like "insert into t values (1, 2, 3)", where the |
| * user did not specify a column list. |
| * |
| * @param targetTableDescriptor The descriptor for the table being |
| * inserted into |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void bindResultColumnsByPosition(TableDescriptor targetTableDescriptor) |
| throws StandardException |
| { |
| int size = size(); |
| |
| for (int index = 0; index < size; index++) |
| { |
| /* |
| ** Add one to the iterator index, because iterator indexes start at zero, |
| ** and column numbers start at one. |
| */ |
| elementAt(index).bindResultColumnByPosition( |
| targetTableDescriptor, |
| index + 1); |
| } |
| } |
| |
| /** |
| * Preprocess the expression trees under the RCL. |
| * We do a number of transformations |
| * here (including subqueries, IN lists, LIKE and BETWEEN) plus |
| * subquery flattening. |
| * NOTE: This is done before the outer ResultSetNode is preprocessed. |
| * |
| * @param numTables Number of tables in the DML Statement |
| * @param outerFromList FromList from outer query block |
| * @param outerSubqueryList SubqueryList from outer query block |
| * @param outerPredicateList PredicateList from outer query block |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void preprocess(int numTables, |
| FromList outerFromList, |
| SubqueryList outerSubqueryList, |
| PredicateList outerPredicateList) |
| throws StandardException |
| { |
| int size = size(); |
| |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn resultColumn = elementAt(index); |
| setElementAt(resultColumn.preprocess(numTables, |
| outerFromList, |
| outerSubqueryList, |
| outerPredicateList), |
| index); |
| } |
| } |
| |
| /** |
| Verify that all the result columns have expressions that |
| are storable for them. Check versus the given ResultColumnList. |
| |
| @exception StandardException Thrown on error |
| */ |
| void checkStorableExpressions(ResultColumnList toStore) |
| throws StandardException |
| { |
| int size = size(); |
| |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn otherRC = toStore.elementAt(index); |
| |
| elementAt(index).checkStorableExpression(otherRC); |
| } |
| } |
| |
| /** |
| Return an array holding the 0 based heap offsets of |
| the StreamStorable columns in this ResultColumnList. |
| This returns null if this list does not contain any |
| StreamStorableColumns. The list this returns does not |
| contain duplicates. This should only be used for |
| a resultColumnList the refers to a single heap |
| such as the target for an Insert, Update or Delete. |
| @param heapColCount the number of heap columns |
| @exception StandardException Thrown on error |
| */ |
| int[] getStreamStorableColIds(int heapColCount) throws StandardException |
| { |
| //@#$ |
| //System.out.println("getStreamStorableColids"); |
| |
| int ssCount = 0; |
| boolean[] isSS = new boolean[heapColCount];//Should be table length. |
| |
| for (ResultColumn rc : this) |
| { |
| if (rc.getTypeId().streamStorable()) |
| { |
| //System.out.println(" streamStorable=true"); |
| ColumnDescriptor cd = rc.getTableColumnDescriptor(); |
| isSS[cd.getPosition()-1] = true; |
| } |
| } |
| |
| for (int ix=0;ix<isSS.length;ix++) if (isSS[ix]) ssCount++; |
| |
| if (ssCount==0)return null; |
| |
| int[] result = new int[ssCount]; |
| int resultOffset=0; |
| for (int heapOffset=0;heapOffset<isSS.length;heapOffset++) |
| { |
| if (isSS[heapOffset]) |
| result[resultOffset++]=heapOffset; |
| } |
| |
| return result; |
| } |
| |
| /** |
| Verify that all the result columns have expressions that |
| are storable for them. Check versus the expressions under the |
| ResultColumns. |
| |
| @exception StandardException Thrown on error |
| */ |
| void checkStorableExpressions() |
| throws StandardException |
| { |
| for (ResultColumn rc : this) |
| { |
| rc.checkStorableExpression(); |
| } |
| } |
| |
| |
| /** |
| * Generate the code to place the columns' values into |
| * a row variable named "r". This wrapper is here |
| * rather than in ResultColumn, because that class does |
| * not know about the position of the columns in the list. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| @Override |
| void generate(ActivationClassBuilder acb, MethodBuilder mb) |
| throws StandardException |
| { |
| generateCore(acb, mb, false); |
| } |
| |
| /** |
| * Generate the code to place the columns' values into |
| * a row variable named "r". This wrapper is here |
| * rather than in ResultColumn, because that class does |
| * not know about the position of the columns in the list. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void generateNulls(ActivationClassBuilder acb, |
| MethodBuilder mb) |
| throws StandardException |
| { |
| generateCore(acb, mb, true); |
| } |
| |
| /** |
| * Generate the code to place the columns' values into |
| * a row variable named "r". This wrapper is here |
| * rather than in ResultColumn, because that class does |
| * not know about the position of the columns in the list. |
| * |
| * This is the method that does the work. |
| */ |
| void generateCore(ExpressionClassBuilder acb, |
| MethodBuilder mb, |
| boolean genNulls) |
| throws StandardException |
| { |
| // generate the function and initializer: |
| // private ExecRow fieldX; |
| // In the constructor: |
| // fieldX = getExecutionFactory().getValueRow(# cols); |
| // private ExecRow exprN() |
| // { |
| // fieldX.setColumn(1, col(1).generateColumn(ps))); |
| // ... and so on for each column ... |
| // return fieldX; |
| // } |
| // static Method exprN = method pointer to exprN; |
| |
| // this sets up the method and the static field. |
| MethodBuilder userExprFun = acb.newUserExprFun(); |
| |
| generateEvaluatedRow( acb, userExprFun, genNulls, false ); |
| |
| // what we return is the access of the field, i.e. the pointer to the method. |
| acb.pushMethodReference(mb, userExprFun); |
| } |
| |
| /** |
| * <p> |
| * Generate the code for a method (userExprFun) which creates a row |
| * and, column by column, stuffs it with the evaluated |
| * expressions of our ResultColumns. The method returns the |
| * stuffed row. |
| * </p> |
| * |
| * This is the method that does the work. |
| */ |
| void generateEvaluatedRow |
| ( |
| ExpressionClassBuilder acb, |
| MethodBuilder userExprFun, |
| boolean genNulls, |
| boolean forMatchingClause |
| ) |
| throws StandardException |
| { |
| // generate the function and initializer: |
| // private ExecRow fieldX; |
| // In the constructor: |
| // fieldX = getExecutionFactory().getValueRow(# cols); |
| // private ExecRow exprN() |
| // { |
| // fieldX.setColumn(1, col(1).generateColumn(ps))); |
| // ... and so on for each column ... |
| // return fieldX; |
| // } |
| // static Method exprN = method pointer to exprN; |
| |
| /* Declare the field */ |
| LocalField field = acb.newFieldDeclaration(Modifier.PRIVATE, ClassName.ExecRow); |
| |
| // Generate the code to create the row in the constructor |
| genCreateRow(acb, field, "getValueRow", ClassName.ExecRow, size()); |
| |
| ResultColumn rc; |
| int size = size(); |
| |
| MethodBuilder cb = acb.getConstructor(); |
| |
| for (int index = 0; index < size; index++) |
| { |
| // generate statements of the form |
| // fieldX.setColumn(columnNumber, (DataValueDescriptor) columnExpr); |
| // and add them to exprFun. |
| rc = elementAt(index); |
| |
| /* If we are not generating nulls, then we can skip this RC if |
| * it is simply propagating a column from the source result set. |
| */ |
| if (!genNulls) |
| { |
| ValueNode sourceExpr = rc.getExpression(); |
| |
| if ( sourceExpr instanceof VirtualColumnNode && ! ( ((VirtualColumnNode) sourceExpr).getCorrelated()) ) |
| { |
| continue; |
| } |
| |
| //DERBY-4631 - For INNER JOINs and LEFT OUTER |
| // JOINs, Derby retrieves the join column values |
| // from the left table's join column. But for |
| // RIGHT OUTER JOINs, the join column's value |
| // will be picked up based on following logic. |
| //1)if the left table's column value is null |
| // then pick up the right table's column's value. |
| //2)If the left table's column value is non-null, |
| // then pick up that value |
| if (rc.getJoinResultSet() != null) { |
| //We are dealing with a join column for |
| // RIGHT OUTER JOIN with USING/NATURAL eg |
| // select c from t1 right join t2 using (c) |
| //We are talking about column c as in "select c" |
| ResultColumnList jnRCL = |
| rc.getJoinResultSet().getResultColumns(); |
| int joinResultSetNumber = |
| rc.getJoinResultSet().getResultSetNumber(); |
| |
| //We need to know the column positions of left |
| // table's join column and right table's join |
| // column to generate the code explained above |
| int virtualColumnIdRightTable = -1; |
| int virtualColumnIdLeftTable = -1; |
| |
| for (ResultColumn joinColumn : jnRCL) { |
| if (joinColumn.getName().equals(rc.getUnderlyingOrAliasName())) { |
| if (joinColumn.isRightOuterJoinUsingClause()) |
| virtualColumnIdRightTable = joinColumn.getVirtualColumnId(); |
| else |
| virtualColumnIdLeftTable = joinColumn.getVirtualColumnId(); |
| } |
| } |
| |
| userExprFun.getField(field); // instance |
| userExprFun.push(index + 1); // arg1 |
| |
| String resultTypeName = |
| getTypeCompiler( |
| DataTypeDescriptor.getBuiltInDataTypeDescriptor( |
| Types.BOOLEAN).getTypeId()).interfaceName(); |
| String receiverType = ClassName.DataValueDescriptor; |
| |
| //Our plan is to generate DERBY-4631 |
| // if(lefTablJoinColumnValue is null) |
| // then |
| // use rightTablJoinColumnValue |
| // else |
| // use lefTablJoinColumnValue |
| |
| //Following will generate |
| // if(lefTablJoinColumnValue is null) |
| acb.pushColumnReference(userExprFun, joinResultSetNumber, |
| virtualColumnIdLeftTable); |
| userExprFun.cast(rc.getTypeCompiler().interfaceName()); |
| userExprFun.cast(receiverType); |
| userExprFun.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, |
| "isNullOp",resultTypeName, 0); |
| //Then call generateExpression on left Table's column |
| userExprFun.cast(ClassName.BooleanDataValue); |
| userExprFun.push(true); |
| userExprFun.callMethod( |
| VMOpcode.INVOKEINTERFACE, (String) null, "equals", "boolean", 1); |
| //Following will generate |
| // then |
| // use rightTablJoinColumnValue |
| userExprFun.conditionalIf(); |
| acb.pushColumnReference(userExprFun, joinResultSetNumber, |
| virtualColumnIdRightTable); |
| userExprFun.cast(rc.getTypeCompiler().interfaceName()); |
| //Following will generate |
| // else |
| // use lefTablJoinColumnValue |
| userExprFun.startElseCode(); |
| acb.pushColumnReference(userExprFun, joinResultSetNumber, |
| virtualColumnIdLeftTable); |
| userExprFun.cast(rc.getTypeCompiler().interfaceName()); |
| userExprFun.completeConditional(); |
| userExprFun.cast(ClassName.DataValueDescriptor); |
| userExprFun.callMethod( |
| VMOpcode.INVOKEINTERFACE, ClassName.Row, "setColumn", "void", 2); |
| continue; |
| } |
| |
| if ( !forMatchingClause ) |
| { |
| if (sourceExpr instanceof ColumnReference && ! ( ((ColumnReference) sourceExpr).getCorrelated())) |
| { |
| continue; |
| } |
| } |
| } |
| |
| |
| // row add is 1-based, and iterator index is 0-based |
| if (SanityManager.DEBUG) |
| { |
| if (index + 1 != rc.getVirtualColumnId()) |
| { |
| SanityManager.THROWASSERT( |
| "VirtualColumnId (" + |
| rc.getVirtualColumnId() + |
| ") does not agree with position within Vector (" + |
| (index + 1) + |
| ")"); |
| } |
| } |
| |
| // |
| // Generated columns should be populated after the base row because |
| // the generation clauses may refer to base columns that have to be filled |
| // in first. Population of generated columns is done in another |
| // method, which (like CHECK CONSTRAINTS) is explicitly called by |
| // InsertResultSet and UpdateResultSet. |
| // |
| // For LEFT JOINs, we may need to stuff a NULL into the generated column slot, |
| // just as we do for non-generated columns in a LEFT JOIN. We look at the source |
| // expression for the ResultColumn to determine whether this ResultColumnList |
| // represents an INSERT/UPDATE vs. a SELECT. If this ResultColumnList represents a |
| // LEFT JOIN, then the source expression will be a VirtualColumnNode. |
| // See DERBY-6346. |
| // |
| if ( rc.hasGenerationClause() ) |
| { |
| ValueNode expr = rc.getExpression(); |
| if ( (expr != null) && !(expr instanceof VirtualColumnNode) ) |
| { |
| continue; |
| } |
| } |
| |
| // we need the expressions to be Columns exactly. |
| |
| /* SPECIAL CASE: Expression is a non-null constant. |
| * Generate the setColumn() call in the constructor |
| * so that it will only be executed once per instantiation. |
| * |
| * Increase the statement counter in constructor. Code size in |
| * constructor can become too big (more than 64K) for Java compiler |
| * to handle (beetle 4293). We set constant columns in other |
| * methods if constructor has too many statements already. |
| */ |
| if ( (! genNulls) && |
| (rc.getExpression() instanceof ConstantNode) && |
| ! ((ConstantNode) rc.getExpression()).isNull() && |
| ! cb.statementNumHitLimit(1)) |
| { |
| |
| |
| cb.getField(field); // instance |
| cb.push(index + 1); // first arg; |
| |
| rc.generateExpression(acb, cb); |
| cb.cast(ClassName.DataValueDescriptor); // second arg |
| cb.callMethod(VMOpcode.INVOKEINTERFACE, ClassName.Row, "setColumn", "void", 2); |
| continue; |
| } |
| |
| userExprFun.getField(field); // instance |
| userExprFun.push(index + 1); // arg1 |
| |
| /* We want to reuse the null values instead of doing a new each time |
| * if the caller said to generate nulls or the underlying expression |
| * is a typed null value. |
| */ |
| boolean needDVDCast = true; |
| |
| if (rc.isAutoincrementGenerated()) |
| { |
| // (com.ibm.db2j.impl... DataValueDescriptor) |
| // this.getSetAutoincValue(column_number) |
| |
| userExprFun.pushThis(); |
| |
| userExprFun.push(rc.getColumnPosition()); |
| userExprFun.push(rc.getTableColumnDescriptor().getAutoincInc()); |
| |
| userExprFun.callMethod(VMOpcode.INVOKEVIRTUAL, ClassName.BaseActivation, |
| "getSetAutoincrementValue", ClassName.DataValueDescriptor, 2); |
| needDVDCast = false; |
| |
| } |
| else if (genNulls || |
| ((rc.getExpression() instanceof ConstantNode) && |
| ((ConstantNode) rc.getExpression()).isNull())) |
| { |
| userExprFun.getField(field); |
| userExprFun.push(index + 1); |
| userExprFun.callMethod(VMOpcode.INVOKEINTERFACE, ClassName.Row, "getColumn", |
| ClassName.DataValueDescriptor, 1); // the express |
| |
| acb.generateNullWithExpress(userExprFun, rc.getTypeCompiler(), |
| rc.getTypeServices().getCollationType()); |
| } |
| else |
| { |
| rc.generateExpression(acb, userExprFun); |
| } |
| if (needDVDCast) |
| userExprFun.cast(ClassName.DataValueDescriptor); |
| |
| userExprFun.callMethod(VMOpcode.INVOKEINTERFACE, ClassName.Row, "setColumn", "void", 2); |
| } |
| |
| userExprFun.getField(field); |
| userExprFun.methodReturn(); |
| |
| // we are now done modifying userExprFun |
| userExprFun.complete(); |
| } |
| |
| /** |
| * Build an empty row with the size and shape of the ResultColumnList. |
| * |
| * @return an empty row of the correct size and shape. |
| * @exception StandardException Thrown on error |
| */ |
| public ExecRow buildEmptyRow() |
| throws StandardException |
| { |
| int columnCount = size(); |
| ExecRow row = getExecutionFactory().getValueRow( columnCount ); |
| int position = 1; |
| |
| for (ResultColumn rc : this) |
| { |
| DataTypeDescriptor dataType = rc.getTypeServices(); |
| DataValueDescriptor dataValue = dataType.getNull(); |
| |
| row.setColumn( position++, dataValue ); |
| } |
| |
| return row; |
| } |
| |
| /** |
| * Build an empty index row for the given conglomerate. |
| * |
| * @return an empty row of the correct size and shape. |
| * @exception StandardException Thrown on error |
| */ |
| public ExecRow buildEmptyIndexRow(TableDescriptor td, |
| ConglomerateDescriptor cd, |
| StoreCostController scc, |
| DataDictionary dd) |
| throws StandardException |
| { |
| ResultColumn rc; |
| |
| if (SanityManager.DEBUG) |
| { |
| if (! cd.isIndex()) |
| { |
| SanityManager.THROWASSERT("ConglomerateDescriptor expected to be for index: " + cd); |
| } |
| } |
| |
| int[] baseCols = cd.getIndexDescriptor().baseColumnPositions(); |
| ExecRow row = getExecutionFactory().getValueRow(baseCols.length + 1); |
| |
| for (int i = 0; i < baseCols.length; i++) |
| { |
| ColumnDescriptor coldes = td.getColumnDescriptor(baseCols[i]); |
| DataTypeDescriptor dataType = coldes.getType(); |
| |
| // rc = getResultColumn(baseCols[i]); |
| // rc = (ResultColumn) at(baseCols[i] - 1); |
| // dataType = rc.getTypeServices(); |
| DataValueDescriptor dataValue = dataType.getNull(); |
| |
| row.setColumn(i + 1, dataValue ); |
| } |
| |
| RowLocation rlTemplate = scc.newRowLocationTemplate(); |
| |
| row.setColumn(baseCols.length + 1, rlTemplate); |
| |
| return row; |
| } |
| |
| /** |
| * Build an {@code ExecRowBuilder} instance that produces a row of the |
| * same shape as this result column list. |
| * |
| * @param referencedCols a bit map that tells which columns in the |
| * source result set that are used, or {@code null} if all are used |
| * @param skipPropagatedCols whether to skip virtual columns whose |
| * source is the immediate child result set |
| * @return an instance that produces rows of the same shape as this |
| * result column list |
| */ |
| ExecRowBuilder buildRowTemplate(FormatableBitSet referencedCols, |
| boolean skipPropagatedCols) |
| throws StandardException |
| { |
| int columns = (referencedCols == null) ? |
| size() : referencedCols.getNumBitsSet(); |
| |
| ExecRowBuilder builder = new ExecRowBuilder(columns, indexRow); |
| |
| // Get the index of the first column to set in the row template. |
| int colNum = (referencedCols == null) ? 0 : referencedCols.anySetBit(); |
| |
| for (ResultColumn rc : this) { |
| ValueNode sourceExpr = rc.getExpression(); |
| |
| if (sourceExpr instanceof CurrentRowLocationNode) { |
| builder.setColumn(colNum + 1, newRowLocationTemplate()); |
| } else if (skipPropagatedCols && |
| sourceExpr instanceof VirtualColumnNode) { |
| // Skip over those columns whose source is the immediate |
| // child result set. (No need to generate a wrapper |
| // for a SQL NULL when we are smart enough not to pass |
| // that wrapper to the store.) |
| continue; |
| } else { |
| builder.setColumn(colNum + 1, rc.getType()); |
| } |
| |
| // Get the index of the next column to set in the row template. |
| if (referencedCols == null) { |
| colNum++; |
| } else { |
| colNum = referencedCols.anySetBit(colNum); |
| } |
| } |
| |
| return builder; |
| } |
| |
| /** |
| * Shorthand for {@code buildRowTemplate(null, false)}. |
| */ |
| ExecRowBuilder buildRowTemplate() throws StandardException { |
| return buildRowTemplate(null, false); |
| } |
| |
| /** |
| * Generate the code to create an empty row in the constructor. |
| * |
| * @param acb The ACB. |
| * @param field The field for the new row. |
| * @param rowAllocatorMethod The method to call. |
| * @param rowAllocatorType The row type. |
| * @param numCols The number of columns in the row. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| private void genCreateRow(ExpressionClassBuilder acb, |
| LocalField field, |
| String rowAllocatorMethod, |
| String rowAllocatorType, |
| int numCols) |
| throws StandardException |
| { |
| // Create the row in the constructor |
| // fieldX = getExecutionFactory().getValueRow(# cols); |
| |
| MethodBuilder cb = acb.getConstructor(); |
| |
| acb.pushGetExecutionFactoryExpression(cb); // instance |
| cb.push(numCols); |
| cb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, |
| rowAllocatorMethod, rowAllocatorType, 1); |
| cb.setField(field); |
| /* Increase the statement counter in constructor. Code size in |
| * constructor can become too big (more than 64K) for Java compiler |
| * to handle (beetle 4293). We set constant columns in other |
| * methods if constructor has too many statements already. |
| */ |
| cb.statementNumHitLimit(1); // ignore return value |
| } |
| |
| /** |
| * Create a row location template of the right type for the source |
| * conglomerate. |
| */ |
| private RowLocation newRowLocationTemplate() throws StandardException { |
| LanguageConnectionContext lcc = getLanguageConnectionContext(); |
| DataDictionary dd = lcc.getDataDictionary(); |
| |
| int isolationLevel = (dd.getCacheMode() == DataDictionary.DDL_MODE) ? |
| TransactionController.ISOLATION_READ_COMMITTED : |
| TransactionController.ISOLATION_NOLOCK; |
| |
| ConglomerateController cc = |
| lcc.getTransactionCompile().openConglomerate( |
| conglomerateId, |
| false, |
| 0, |
| TransactionController.MODE_RECORD, |
| isolationLevel); |
| |
| try { |
| return cc.newRowLocationTemplate(); |
| } finally { |
| cc.close(); |
| } |
| } |
| |
| /** |
| * Make a ResultDescription for use in a ResultSet. |
| * This is useful when generating/executing a NormalizeResultSet, since |
| * it can appear anywhere in the tree. |
| * |
| * @return A ResultDescription for this ResultSetNode. |
| */ |
| ResultColumnDescriptor[] makeResultDescriptors() |
| { |
| ResultColumnDescriptor colDescs[] = new ResultColumnDescriptor[size()]; |
| int size = size(); |
| |
| for (int index = 0; index < size; index++) |
| { |
| // the ResultColumn nodes are descriptors, so take 'em... |
| colDescs[index] = getExecutionFactory().getResultColumnDescriptor(((ResultColumnDescriptor) elementAt(index))); |
| } |
| |
| return colDescs; |
| } |
| |
| /** |
| * Expand any *'s in the ResultColumnList. In addition, we will guarantee that |
| * each ResultColumn has a name. (All generated names will be unique across the |
| * entire statement.) |
| * |
| * |
| * @exception StandardException Thrown on error |
| */ |
| |
| void expandAllsAndNameColumns(FromList fromList) |
| throws StandardException |
| { |
| boolean expanded = false; |
| ResultColumnList allExpansion; |
| TableName fullTableName; |
| |
| /* First walk result column list looking for *'s to expand */ |
| for (int index = 0; index < size(); index++) |
| { |
| ResultColumn rc = elementAt(index); |
| if (rc instanceof AllResultColumn) |
| { |
| expanded = true; |
| |
| //fullTableName = ((AllResultColumn) rc).getFullTableName(); |
| TableName temp = rc.getTableNameObject(); |
| if(temp != null) { |
| String sName = temp.getSchemaName(); |
| String tName = temp.getTableName(); |
| fullTableName = makeTableName(sName,tName); |
| } |
| else |
| fullTableName = null; |
| allExpansion = fromList.expandAll(fullTableName); |
| |
| /* Make sure that every column has a name */ |
| allExpansion.nameAllResultColumns(); |
| |
| /* Replace the AllResultColumn with the expanded list. |
| * We will update the VirtualColumnIds once below. |
| */ |
| removeElementAt(index); |
| for (int inner = 0; inner < allExpansion.size(); inner++) |
| { |
| insertElementAt(allExpansion.elementAt(inner), index + inner); |
| } |
| |
| // Move the index position to account for the removals and the |
| // insertions. Should be positioned on the last column in the |
| // expansion to prevent double processing of the columns. |
| // DERBY-4410: If the expansion is empty, this will move the |
| // position one step back because the * was removed and nothing |
| // was inserted, so all columns to the right of the current |
| // position have been moved one position to the left. If we |
| // don't adjust the position, we end up skipping columns. |
| index += (allExpansion.size() - 1); |
| |
| // If the rc was a "*", we need to set the initial list size |
| // to the number of columns that are actually returned to |
| // the user. |
| markInitialSize(); |
| } |
| else |
| { |
| /* Make sure that every column has a name */ |
| rc.guaranteeColumnName(); |
| } |
| } |
| |
| /* Go back and update the VirtualColumnIds if we expanded any *'s */ |
| if (expanded) |
| { |
| int size = size(); |
| |
| for (int index = 0; index < size; index++) |
| { |
| /* Vectors are 0-based, VirtualColumnIds are 1-based. */ |
| elementAt(index).setVirtualColumnId(index + 1); |
| } |
| } |
| } |
| |
| /** |
| * Generate (unique across the entire statement) column names for those |
| * ResultColumns in this list which are not named. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void nameAllResultColumns() |
| throws StandardException |
| { |
| for (ResultColumn rc : this) |
| { |
| rc.guaranteeColumnName(); |
| } |
| } |
| |
| |
| /** |
| ** Check whether the column lengths and types of the result columns |
| ** match the expressions under those columns. This is useful for |
| ** INSERT and UPDATE statements. For SELECT statements this method |
| ** should always return true. There is no need to call this for a |
| ** DELETE statement. |
| ** NOTE: We skip over generated columns since they won't have a |
| ** column descriptor. |
| ** |
| ** @return true means all the columns match their expressions, |
| ** false means at least one column does not match its |
| ** expression |
| */ |
| |
| boolean columnTypesAndLengthsMatch() |
| throws StandardException |
| { |
| for (ResultColumn resultColumn : this) |
| { |
| /* Skip over generated columns */ |
| if (resultColumn.isGenerated()) |
| { |
| continue; |
| } |
| |
| if (! resultColumn.columnTypeAndLengthMatch()) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| boolean columnTypesAndLengthsMatch(ResultColumnList otherRCL) |
| throws StandardException |
| { |
| boolean retval = true; |
| |
| /* We check every RC, even after finding 1 that requires |
| * normalization, because the conversion of constants to |
| * the appropriate type occurs under this loop. |
| */ |
| int size = size(); |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn resultColumn = elementAt(index); |
| |
| ResultColumn otherResultColumn = otherRCL.elementAt(index); |
| |
| /* Skip over generated columns */ |
| if (resultColumn.isGenerated() || otherResultColumn.isGenerated()) |
| { |
| continue; |
| } |
| |
| if (! resultColumn.columnTypeAndLengthMatch(otherResultColumn)) |
| { |
| retval = false; |
| } |
| } |
| |
| return retval; |
| } |
| |
| /** |
| * Determine whether this RCL is a No-Op projection of the given RCL. |
| * It only makes sense to do this if the given RCL is from the child |
| * result set of the ProjectRestrict that this RCL is from. |
| * |
| * @param childRCL The ResultColumnList of the child result set. |
| * |
| * @return true if this RCL is a No-Op projection of the given RCL. |
| */ |
| boolean nopProjection(ResultColumnList childRCL) |
| { |
| /* |
| ** This RCL is a useless projection if each column in the child |
| ** if the same as the column in this RCL. This is impossible |
| ** if the two RCLs have different numbers of columns. |
| */ |
| if (this.size() != childRCL.size()) |
| { |
| return false; |
| } |
| |
| /* |
| ** The two lists have the same numbers of elements. Are the lists |
| ** identical? In other words, is the expression in every ResultColumn |
| ** in the PRN's RCL a ColumnReference that points to the corresponding |
| ** column in the child? |
| */ |
| int size = size(); |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn thisColumn = elementAt(index); |
| ResultColumn referencedColumn; |
| |
| /* |
| ** A No-Op projection can point to a VirtualColumnNode or a |
| ** ColumnReference. |
| */ |
| if (thisColumn.getExpression() instanceof VirtualColumnNode) |
| { |
| referencedColumn = |
| ((VirtualColumnNode) (thisColumn.getExpression())). |
| getSourceColumn(); |
| } |
| else if (thisColumn.getExpression() instanceof ColumnReference) |
| { |
| referencedColumn = |
| ((ColumnReference) (thisColumn.getExpression())). |
| getSource(); |
| } |
| else |
| { |
| return false; |
| } |
| |
| ResultColumn childColumn = childRCL.elementAt(index); |
| |
| if (referencedColumn != childColumn) |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Create a shallow copy of a ResultColumnList and its ResultColumns. |
| * (All other pointers are preserved.) |
| * Useful for building new ResultSetNodes during preprocessing. |
| * |
| * @return None. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| ResultColumnList copyListAndObjects() |
| throws StandardException |
| { |
| ResultColumnList newList = new ResultColumnList(getContextManager()); |
| |
| /* Walk the current list - for each ResultColumn in the list, make a copy |
| * and add it to the new list. |
| */ |
| for (ResultColumn origResultColumn: this) |
| { |
| newList.addResultColumn(origResultColumn.cloneMe()); |
| } |
| newList.copyOrderBySelect(this); |
| return newList; |
| } |
| |
| /** |
| * Remove any columns that may have been added for an order by clause. |
| * In a query like: |
| * <pre>select a from t order by b</pre> b is added to the select list |
| * However in the final projection, after the sort is complete, b will have |
| * to be removed. |
| * |
| */ |
| void removeOrderByColumns() |
| { |
| int idx = size() - 1; |
| for (int i = 0; i < orderBySelect; i++, idx--) { |
| removeElementAt(idx); |
| } |
| orderBySelect = 0; |
| } |
| |
| /** |
| * Walk the list and replace ResultColumn.expression with a new |
| * VirtualColumnNode. This is useful when propagating a ResultColumnList |
| * up the query tree. |
| * NOTE: This flavor marks all of the underlying RCs as referenced. |
| * |
| * @param sourceResultSet ResultSetNode that is source of value |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void genVirtualColumnNodes(ResultSetNode sourceResultSet, |
| ResultColumnList sourceResultColumnList) |
| throws StandardException |
| { |
| genVirtualColumnNodes(sourceResultSet, sourceResultColumnList, true); |
| } |
| |
| |
| |
| /** |
| * Walk the list and replace ResultColumn.expression with a new |
| * VirtualColumnNode. This is useful when propagating a ResultColumnList |
| * up the query tree. |
| * |
| * @param sourceResultSet ResultSetNode that is source of value |
| * @param markReferenced Whether or not to mark the underlying RCs |
| * as referenced |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void genVirtualColumnNodes(ResultSetNode sourceResultSet, |
| ResultColumnList sourceResultColumnList, |
| boolean markReferenced) |
| throws StandardException |
| { |
| int size = size(); |
| |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn resultColumn = elementAt(index); |
| |
| /* Vectors are 0-based, VirtualColumnIds are 1-based */ |
| resultColumn.setExpression |
| ( |
| new VirtualColumnNode |
| ( |
| sourceResultSet, |
| sourceResultColumnList.elementAt(index), |
| index + 1, |
| getContextManager() |
| ) |
| ); |
| |
| /* Mark the ResultColumn as being referenced */ |
| if (markReferenced) |
| { |
| resultColumn.setReferenced(); |
| } |
| } |
| } |
| |
| /** |
| * Walk the list and adjust the virtualColumnIds in the ResultColumns |
| * by the specified amount. If ResultColumn.expression is a VirtualColumnNode, |
| * then we adjust the columnId there as well. |
| * |
| * @param adjust The size of the increment. |
| */ |
| void adjustVirtualColumnIds(int adjust) |
| { |
| for (ResultColumn resultColumn : this) |
| { |
| resultColumn.adjustVirtualColumnId(adjust); |
| if (SanityManager.DEBUG) |
| { |
| if ( ! |
| (resultColumn.getExpression() instanceof VirtualColumnNode)) |
| { |
| SanityManager.THROWASSERT( |
| "resultColumn.getExpression() is expected to be " + |
| "instanceof VirtualColumnNode" + |
| " not " + |
| resultColumn.getExpression().getClass().getName()); |
| } |
| } |
| |
| ((VirtualColumnNode) resultColumn.getExpression()).columnId += adjust; |
| } |
| } |
| |
| /** |
| * Project out any unreferenced ResultColumns from the list and |
| * reset the virtual column ids in the referenced ResultColumns. |
| * If all ResultColumns are projected out, then the list is not empty. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void doProjection() throws StandardException |
| { |
| int numDeleted = 0; |
| ResultColumnList deletedRCL = new ResultColumnList(getContextManager()); |
| |
| for (ResultColumn resultColumn : this) |
| { |
| /* RC's for FromBaseTables are marked as referenced during binding. |
| * For other nodes, namely JoinNodes, we need to go 1 level |
| * down the RC/VCN chain to see if the RC is referenced. This is |
| * because we propagate the referencing info from the bottom up. |
| */ |
| if ((! resultColumn.isReferenced()) && |
| (resultColumn.getExpression() instanceof VirtualColumnNode) && |
| !(((VirtualColumnNode) resultColumn.getExpression()).getSourceColumn().isReferenced())) |
| { |
| // Remember the RC to delete when done |
| deletedRCL.addElement(resultColumn); |
| |
| /* Remember how many we have deleted and decrement the |
| * VirtualColumnIds for all nodes which appear after us |
| * in the list. |
| */ |
| numDeleted++; |
| } |
| else |
| { |
| /* Decrement the VirtualColumnId for each node in the list |
| * after the 1st deleted one. |
| */ |
| if (numDeleted >= 1) |
| resultColumn.adjustVirtualColumnId( - numDeleted); |
| /* Make sure that the RC is marked as referenced! */ |
| resultColumn.setReferenced(); |
| } |
| } |
| |
| // Go back and delete the RCs to be delete from the list |
| for (int index = 0; index < deletedRCL.size(); index++) |
| { |
| removeElement(deletedRCL.elementAt(index)); |
| } |
| } |
| |
| /** |
| * Check the uniqueness of the column names within a column list. |
| * |
| * @param errForGenCols Raise an error for any generated column names. |
| * |
| * @return String The first duplicate column name, if any. |
| */ |
| String verifyUniqueNames(boolean errForGenCols) |
| throws StandardException |
| { |
| HashSet<String> seenNames = new HashSet<String>(size() + 2, 0.999f); |
| |
| for (ResultColumn rc : this) |
| { |
| if (errForGenCols && rc.isNameGenerated()) |
| throw StandardException.newException(SQLState.LANG_DB2_VIEW_REQUIRES_COLUMN_NAMES); |
| /* Verify that this column's name is unique within the list */ |
| String colName = rc.getName(); |
| boolean alreadySeen = !seenNames.add(colName); |
| |
| if (alreadySeen) |
| { |
| return colName; |
| } |
| } |
| |
| /* No duplicate column names */ |
| return null; |
| } |
| |
| /** |
| * Validate the derived column list (DCL) and propagate the info |
| * from the list to the final ResultColumnList. |
| * |
| * @param derivedRCL The derived column list |
| * @param tableName The table name for the FromTable |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void propagateDCLInfo(ResultColumnList derivedRCL, String tableName) |
| throws StandardException |
| { |
| String duplicateColName; |
| |
| /* Do both lists, if supplied by user, have the same degree? */ |
| if (derivedRCL.size() != size() && |
| ! derivedRCL.getCountMismatchAllowed()) |
| { |
| if (visibleSize() != derivedRCL.visibleSize()) { |
| throw StandardException.newException(SQLState.LANG_DERIVED_COLUMN_LIST_MISMATCH, tableName); |
| } |
| } |
| |
| /* Check the uniqueness of the column names within the derived list */ |
| duplicateColName = derivedRCL.verifyUniqueNames(false); |
| if (duplicateColName != null) |
| { |
| throw StandardException.newException(SQLState.LANG_DUPLICATE_COLUMN_NAME_DERIVED, duplicateColName); |
| } |
| |
| /* We can finally copy the derived names into the final list */ |
| copyResultColumnNames(derivedRCL); |
| } |
| |
| /** |
| * Look for and reject ? parameters under ResultColumns. This is done for |
| * SELECT statements. |
| * |
| * @exception StandardException Thrown if a ? parameter found directly |
| * under a ResultColumn |
| */ |
| |
| void rejectParameters() throws StandardException |
| { |
| for (ResultColumn rc : this) |
| { |
| rc.rejectParameter(); |
| } |
| } |
| |
| /** |
| * Check for (and reject) XML values directly under the ResultColumns. |
| * This is done for SELECT/VALUES statements. We reject values |
| * in this case because JDBC does not define an XML type/binding |
| * and thus there's no standard way to pass such a type back |
| * to a JDBC application. |
| * |
| * Note that we DO allow an XML column in a top-level RCL |
| * IF that column was added to the RCL by _us_ instead of |
| * by the user. For example, if we have a table: |
| * |
| * create table t1 (i int, x xml) |
| * |
| * and the user query is: |
| * |
| * select i from t1 order by x |
| * |
| * the "x" column will be added (internally) to the RCL |
| * as part of ORDER BY processing--and so we need to |
| * allow that XML column to be bound without throwing |
| * an error. If, as in this case, the XML column reference |
| * is invalid (we can't use ORDER BY on an XML column because |
| * XML values aren't ordered), a more appropriate error |
| * message should be returned to the user in later processing. |
| * If we didn't allow for this, the user would get an |
| * error saying that XML columns are not valid as part |
| * of the result set--but as far as s/he knows, there |
| * isn't such a column: only "i" is supposed to be returned |
| * (the RC for "x" was added to the RCL by _us_ as part of |
| * ORDER BY processing). |
| * |
| * ASSUMPTION: Any RCs that are generated internally and |
| * added to this RCL (before this RCL is bound) are added |
| * at the _end_ of the list. If that's true, then any |
| * RC with an index greater than the size of the initial |
| * (user-specified) list must have been added internally |
| * and will not be returned to the user. |
| * |
| * @exception StandardException Thrown if an XML value found |
| * directly under a ResultColumn |
| */ |
| void rejectXMLValues() throws StandardException |
| { |
| int sz = size(); |
| |
| for (int i = 1; i <= sz; i++) { |
| |
| if (i > initialListSize) |
| // this RC was generated internally and will not |
| // be returned to the user, so don't throw error. |
| continue; |
| |
| ResultColumn rc = getResultColumn(i); |
| if ((rc != null) && (rc.getType() != null) && |
| rc.getType().getTypeId().isXMLTypeId()) |
| { // Disallow it. |
| throw StandardException.newException( |
| SQLState.LANG_ATTEMPT_TO_SELECT_XML); |
| } |
| |
| } |
| } |
| |
| /** |
| * Set the resultSetNumber in all of the ResultColumns. |
| * |
| * @param resultSetNumber The resultSetNumber |
| */ |
| void setResultSetNumber(int resultSetNumber) |
| { |
| for (ResultColumn rc : this) |
| { |
| rc.setResultSetNumber(resultSetNumber); |
| } |
| } |
| |
| /** |
| * Mark all of the ResultColumns as redundant. |
| * Useful when chopping a ResultSetNode out of a tree when there are |
| * still references to its RCL. |
| */ |
| void setRedundant() |
| { |
| for (ResultColumn rc : this) |
| { |
| rc.setRedundant(); |
| } |
| } |
| |
| /** |
| * Verify that all of the columns in the SET clause of a positioned update |
| * appear in the cursor's FOR UPDATE OF list. |
| * |
| * @param cursorStmt the statement that owns the cursor |
| * @param cursorName The cursor's name. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void checkColumnUpdateability( |
| ExecPreparedStatement cursorStmt, String cursorName) |
| throws StandardException |
| { |
| for (ResultColumn resultColumn : this) |
| { |
| if (resultColumn.updated() && |
| !cursorStmt.isUpdateColumn(resultColumn.getName())) |
| { |
| throw StandardException.newException(SQLState.LANG_COLUMN_NOT_UPDATABLE_IN_CURSOR, |
| resultColumn.getName(), |
| cursorName); |
| } |
| } |
| } |
| |
| /** |
| * Set up the result expressions for a UNION, INTERSECT, or EXCEPT: |
| * o Verify union type compatiblity |
| * o Get dominant type for result (type + max length + nullability) |
| * o Create a new ColumnReference with dominant type and name of from this |
| * RCL and make that the new expression. |
| * o Set the type info for in the ResultColumn to the dominant type |
| * |
| * NOTE - We are assuming that caller has generated a new RCL for the UNION |
| * with the same names as the left side's RCL and copies of the expressions. |
| * |
| * @param otherRCL RCL from other side of the UNION. |
| * @param tableNumber The tableNumber for the UNION. |
| * @param level The nesting level for the UNION. |
| * @param operatorName "UNION", "INTERSECT", or "EXCEPT" |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void setUnionResultExpression(ResultColumnList otherRCL, |
| int tableNumber, |
| int level, |
| String operatorName) |
| throws StandardException |
| { |
| TableName dummyTN; |
| |
| if (SanityManager.DEBUG) |
| { |
| if (visibleSize() != otherRCL.visibleSize()) |
| { |
| SanityManager.THROWASSERT( |
| "visibleSize() = (" + |
| visibleSize() + |
| ") is expected to equal otherRCL.visibleSize (" + |
| otherRCL.visibleSize() + |
| ")"); |
| } |
| |
| // Generated grouping columns and unselected ORDER BY columns |
| // should have been removed for the RCL of a SetOperatorNode, so |
| // that size and visible size are equal (DERBY-3764). |
| SanityManager.ASSERT(size() == visibleSize(), |
| "size() and visibleSize() should be equal"); |
| } |
| |
| /* Make a dummy TableName to be shared by all new CRs */ |
| dummyTN = new TableName(null, null, getContextManager()); |
| |
| int size = visibleSize(); |
| for (int index = 0; index < size; index++) |
| { |
| ColumnReference newCR; |
| ResultColumn thisRC = elementAt(index); |
| ResultColumn otherRC = otherRCL.elementAt(index); |
| ValueNode thisExpr = thisRC.getExpression(); |
| ValueNode otherExpr = otherRC.getExpression(); |
| |
| // If there is one row that is not 'autoincrement', the Union should |
| // not be 'autoincrement'. |
| if (!otherRC.isAutoincrementGenerated() && thisRC.isAutoincrementGenerated()) |
| { |
| thisRC.resetAutoincrementGenerated(); |
| } |
| /* |
| ** If there are ? parameters in the ResultColumnList of a row |
| ** in a table constructor, their types will not be set. Just skip |
| ** these - their types will be set later. Each ? parameter will |
| ** get the type of the first non-? in its column, so it can't |
| ** affect the final dominant type. It's possible that all the |
| ** rows for a particular column will have ? parameters - this is |
| ** an error condition that will be caught later. |
| */ |
| TypeId thisTypeId = thisExpr.getTypeId(); |
| if (thisTypeId == null) |
| continue; |
| |
| TypeId otherTypeId = otherExpr.getTypeId(); |
| if (otherTypeId == null) |
| continue; |
| |
| /* |
| ** Check type compatability. |
| */ |
| ClassFactory cf = getClassFactory(); |
| if ( !unionCompatible( thisExpr, otherExpr ) ) |
| { |
| throw StandardException.newException(SQLState.LANG_NOT_UNION_COMPATIBLE, |
| thisTypeId.getSQLTypeName(), |
| otherTypeId.getSQLTypeName(), |
| operatorName); |
| } |
| |
| DataTypeDescriptor resultType = thisExpr.getTypeServices().getDominantType( |
| otherExpr.getTypeServices(), |
| cf); |
| |
| newCR = new ColumnReference( |
| thisRC.getName(), dummyTN, getContextManager()); |
| newCR.setType(resultType); |
| /* Set the tableNumber and nesting levels in newCR. |
| * If thisExpr is not a CR, then newCR cannot be |
| * correlated, hence source and nesting levels are |
| * the same. |
| */ |
| if (thisExpr instanceof ColumnReference) |
| { |
| newCR.copyFields((ColumnReference) thisExpr); |
| } |
| else |
| { |
| newCR.setNestingLevel(level); |
| newCR.setSourceLevel(level); |
| } |
| newCR.setTableNumber(tableNumber); |
| thisRC.setExpression(newCR); |
| thisRC.setType( |
| thisRC.getTypeServices().getDominantType( |
| otherRC.getTypeServices(), cf)); |
| |
| /* DB2 requires both sides of union to have same name for the result to |
| * have that name. Otherwise, leave it or set it to a generated name */ |
| if (thisRC.getName() != null && !thisRC.isNameGenerated() && |
| otherRC.getName() != null) |
| { |
| /* Result name needs to be changed */ |
| if (otherRC.isNameGenerated()) |
| { |
| thisRC.setName(otherRC.getName()); |
| thisRC.setNameGenerated(true); |
| } |
| else if (!thisRC.getName().equals(otherRC.getName())) |
| { |
| /* Both sides have user specified names that don't match */ |
| thisRC.setName(null); |
| thisRC.guaranteeColumnName(); |
| thisRC.setNameGenerated(true); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Return true if the types of two expressions are union compatible. The rules for union |
| * compatibility are found in the SQL Standard, part 2, section 7.3 (<query expression>), |
| * syntax rule 20.b.ii. That in turn, refers you to section 9.3 (Result of data type combinations). |
| * See, for instance, <a href="https://issues.apache.org/jira/browse/DERBY-4692">DERBY-4692</a>. |
| * |
| * This logic may enforce only a weaker set of rules. Here is the original comment |
| * on the original logic: "We want to make sure that the types are assignable in either direction |
| * and they are comparable." We may need to revisit this code to make it conform to the |
| * Standard. |
| */ |
| private boolean unionCompatible( ValueNode left, ValueNode right ) |
| throws StandardException |
| { |
| TypeId leftTypeId = left.getTypeId(); |
| TypeId rightTypeId = right.getTypeId(); |
| ClassFactory cf = getClassFactory(); |
| |
| if ( |
| !left.getTypeCompiler().storable(rightTypeId, cf) && |
| !right.getTypeCompiler().storable(leftTypeId, cf) |
| ) |
| { return false; } |
| |
| if ( leftTypeId.isBooleanTypeId() != rightTypeId.isBooleanTypeId() ) { return false; } |
| |
| return true; |
| } |
| |
| /** |
| * Do the 2 RCLs have the same type and length. |
| * This is useful for UNIONs when deciding whether a NormalizeResultSet is required. |
| * |
| * @param otherRCL The other RCL. |
| * |
| * @return boolean Whether or not there is an exact UNION type match on the 2 RCLs. |
| */ |
| boolean isExactTypeAndLengthMatch(ResultColumnList otherRCL) |
| throws StandardException |
| { |
| |
| if (SanityManager.DEBUG) { |
| // The visible size of the two RCLs must be equal. |
| SanityManager.ASSERT(visibleSize() == otherRCL.visibleSize(), |
| "visibleSize() should match"); |
| // The generated grouping columns and unselected ORDER BY columns |
| // should have been removed from the RCL of the SetOperatorNode, |
| // so size and visible size should be equal (DERBY-3764). |
| SanityManager.ASSERT(size() == visibleSize(), |
| "size() and visibleSize() should match"); |
| } |
| |
| int size = visibleSize(); |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn thisRC = elementAt(index); |
| ResultColumn otherRC = otherRCL.elementAt(index); |
| |
| if (! thisRC.getTypeServices().isExactTypeAndLengthMatch( |
| otherRC.getTypeServices() )) |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Does the column list contain any of the given column positions |
| * that are updated? Implements same named routine in UpdateList. |
| * |
| * @param columns An array of column positions |
| * |
| * @return True if this column list contains any of the given columns |
| */ |
| public boolean updateOverlaps(int[] columns) |
| { |
| for (ResultColumn rc : this) |
| { |
| if ( ! rc.updated()) |
| continue; |
| |
| int column = rc.getColumnPosition(); |
| |
| for (int i = 0; i < columns.length; i++) |
| { |
| if (columns[i] == column) |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Return an array that contains references to the columns in this list |
| * sorted by position. |
| * |
| * @return The sorted array. |
| */ |
| ResultColumn[] getSortedByPosition() |
| { |
| int size = size(); |
| ResultColumn[] result; |
| |
| /* |
| ** Form an array of the original ResultColumns |
| */ |
| result = new ResultColumn[size]; |
| |
| /* |
| ** Put the ResultColumns in the array |
| */ |
| for (int index = 0; index < size; index++) |
| { |
| result[index] = elementAt(index); |
| } |
| |
| /* |
| ** Sort the array by column position |
| */ |
| java.util.Arrays.sort(result); |
| return result; |
| } |
| |
| /** |
| * Return an array of all my column positions, sorted in |
| * ascending order. |
| * |
| * @return a sorted array |
| */ |
| public int[] sortMe() |
| { |
| ResultColumn[] sortedResultColumns = getSortedByPosition(); |
| int[] sortedColumnIds = new int[sortedResultColumns.length]; |
| for (int ix = 0; ix < sortedResultColumns.length; ix++) |
| { |
| sortedColumnIds[ix] = sortedResultColumns[ix].getColumnPosition(); |
| } |
| return sortedColumnIds; |
| } |
| |
| |
| /** |
| * Expand this ResultColumnList by adding all columns from the given |
| * table that are not in this list. The result is sorted by column |
| * position. |
| * |
| * @param td The TableDescriptor for the table in question |
| * @param tableName The name of the table as given in the query |
| * |
| * @return A new ResultColumnList expanded to include all columns in |
| * the given table. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| ResultColumnList expandToAll(TableDescriptor td, |
| TableName tableName) |
| throws StandardException |
| { |
| ResultColumn rc; |
| ColumnDescriptor cd; |
| ResultColumnList retval; |
| ResultColumn[] originalRCS; |
| int posn; |
| |
| /* Get a new ResultColumnList */ |
| retval = new ResultColumnList(getContextManager()); |
| /* |
| ** Form a sorted array of the ResultColumns |
| */ |
| originalRCS = getSortedByPosition(); |
| |
| posn = 0; |
| |
| /* Iterate through the ColumnDescriptors for the given table */ |
| ColumnDescriptorList cdl = td.getColumnDescriptorList(); |
| int cdlSize = cdl.size(); |
| |
| for (int index = 0; index < cdlSize; index++) |
| { |
| cd = cdl.elementAt(index); |
| |
| if ((posn < originalRCS.length) && |
| (cd.getPosition() == originalRCS[posn].getColumnPosition())) |
| { |
| rc = originalRCS[posn]; |
| posn++; |
| } |
| else |
| { |
| /* Build a ResultColumn/ColumnReference pair for the column */ |
| rc = makeColumnReferenceFromName( tableName, cd.getColumnName() ); |
| |
| /* Bind the new ResultColumn */ |
| rc.bindResultColumnByPosition(td, cd.getPosition()); |
| } |
| |
| /* Add the ResultColumn to the list */ |
| retval.addResultColumn(rc); |
| } |
| |
| if (SanityManager.DEBUG) |
| SanityManager.ASSERT(posn == originalRCS.length, |
| "ResultColumns in original list not added to expanded ResultColumnList"); |
| |
| return retval; |
| } |
| |
| /** |
| * Bind any untyped null nodes to the types in the given ResultColumnList. |
| * Nodes that don't know their type may pass down nulls to |
| * children nodes. In the case of something like a union, it knows |
| * to try its right and left result sets against each other. |
| * But if a null reaches us, it means we have a null type that |
| * we don't know how to handle. |
| * |
| * @param bindingRCL The ResultColumnList with the types to bind to. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void bindUntypedNullsToResultColumns(ResultColumnList bindingRCL) |
| throws StandardException |
| { |
| if (bindingRCL == null) |
| { |
| throw StandardException.newException(SQLState.LANG_NULL_IN_VALUES_CLAUSE); |
| } |
| |
| if (SanityManager.DEBUG) |
| SanityManager.ASSERT(bindingRCL.size() >= this.size(), |
| "More columns in result column list than in base table"); |
| |
| int size = size(); |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn bindingRC = bindingRCL.elementAt(index); |
| ResultColumn thisRC = elementAt(index); |
| |
| thisRC.typeUntypedNullExpression(bindingRC); |
| } |
| } |
| |
| /** |
| * Mark all the columns in this list as updated by an update statement. |
| */ |
| void markUpdated() |
| { |
| for (ResultColumn rc : this) |
| { |
| rc.markUpdated(); |
| } |
| } |
| |
| /** |
| * Mark all the (base) columns in this list as updatable by a positioned update |
| * statement. This is necessary |
| * for positioned update statements, because we expand the column list |
| * to include all the columns in the base table, and we need to be able |
| * to tell which ones the user is really trying to update so we can |
| * determine correctly whether all the updated columns are in the |
| * "for update" list. |
| */ |
| void markUpdatableByCursor() |
| { |
| for (ResultColumn rc : this) |
| { |
| // Determine whether the column is a base column and |
| // not a derived column, and, additionally, |
| // verify that the column was not aliased. |
| // |
| if (rc.getSourceTableName() != null && |
| rc.getExpression() != null && |
| rc.getExpression().getColumnName().equals(rc.getName())) |
| rc.markUpdatableByCursor(); |
| } |
| } |
| |
| /** |
| * Verify that all of the column names in this list are contained |
| * within the ColumnDefinitionNodes within the TableElementList. |
| * |
| * |
| * @return String The 1st column name, if any, that is not in the list. |
| */ |
| String verifyCreateConstraintColumnList(TableElementList tel) |
| { |
| for (ResultColumn rc : this) |
| { |
| String colName = rc.getName(); |
| |
| if (! tel.containsColumnName(colName)) |
| { |
| return colName; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Export the result column names to the passed in String[]. |
| * |
| * @param columnNames String[] to hold the column names. |
| */ |
| void exportNames(String[] columnNames) |
| { |
| if (SanityManager.DEBUG) |
| { |
| if (size() != columnNames.length) |
| { |
| SanityManager.THROWASSERT( |
| "size() (" + |
| size() + |
| ") is expected to equal columnNames.length (" + |
| columnNames.length + |
| ")"); |
| } |
| } |
| |
| int size = size(); |
| |
| for (int index = 0; index < size; index++) |
| { |
| columnNames[index] = elementAt(index).getName(); |
| } |
| } |
| |
| /** |
| * Given a ResultColumn at the next deepest level in the tree, |
| * search this RCL for its parent ResultColumn. |
| * |
| * @param childRC The child ResultColumn |
| * |
| * @return ResultColumn The parent ResultColumn |
| */ |
| ResultColumn findParentResultColumn(ResultColumn childRC) |
| { |
| ResultColumn parentRC = null; |
| |
| for (ResultColumn rc : this) |
| { |
| if (rc.getExpression() instanceof ColumnReference) |
| { |
| ColumnReference cr = (ColumnReference) rc.getExpression(); |
| |
| if (cr.getSource() == childRC) |
| { |
| parentRC = rc; |
| break; |
| } |
| } |
| else if (rc.getExpression() instanceof VirtualColumnNode) |
| { |
| VirtualColumnNode vcn = (VirtualColumnNode) rc.getExpression(); |
| |
| if (vcn.getSourceColumn() == childRC) |
| { |
| parentRC = rc; |
| break; |
| } |
| } |
| |
| } |
| |
| return parentRC; |
| } |
| |
| /** |
| * Mark as updatable all the columns in this result column list |
| * that match the columns in the given update column list. |
| * |
| * @param updateColumns A ResultColumnList representing the columns |
| * to be updated. |
| */ |
| void markUpdated(ResultColumnList updateColumns) |
| { |
| for (ResultColumn updateColumn : updateColumns) |
| { |
| ResultColumn resultColumn = getResultColumn(updateColumn.getName()); |
| |
| /* |
| ** This ResultColumnList may not be bound yet - for update |
| ** statements, we mark the updated columns *before* we bind |
| ** the RCL. This ordering is important because we add columns |
| ** to the RCL after marking the update columns and before |
| ** binding. |
| ** |
| ** So, it can happen that there is an invalid column name in |
| ** the list. This condition will cause an exception when the |
| ** RCL is bound. Just ignore it for now. |
| */ |
| if (resultColumn != null) |
| { |
| resultColumn.markUpdated(); |
| } |
| } |
| } |
| |
| /** |
| * Mark all the columns in the select sql that this result column list represents |
| * as updatable if they match the columns in the given update column list. |
| * |
| * @param updateColumns A list representing the columns |
| * to be updated. |
| */ |
| void markColumnsInSelectListUpdatableByCursor(List<String> updateColumns) |
| { |
| commonCodeForUpdatableByCursor(updateColumns, true); |
| } |
| |
| /** |
| * dealingWithSelectResultColumnList true means we are dealing with |
| * ResultColumnList for a select sql. When dealing with ResultColumnList for |
| * select sql, it is possible that not all the updatable columns are |
| * projected in the select column list and hence it is possible that we may |
| * not find the column to be updated in the ResultColumnList and that is why |
| * special handling is required when dealingWithSelectResultColumnList is true. |
| * eg select c11, c13 from t1 for update of c11, c12 |
| * In the eg above, we will find updatable column c11 in the select column |
| * list but we will not find updatable column c12 in the select column list |
| */ |
| private void commonCodeForUpdatableByCursor( |
| List<String> updateColumns, |
| boolean dealingWithSelectResultColumnList) |
| { |
| /* |
| ** If there is no update column list, or the list is empty, then it means that |
| ** all the columns which have a base table associated with them are updatable. |
| */ |
| if ( (updateColumns == null) || (updateColumns.isEmpty()) ) |
| { |
| markUpdatableByCursor(); |
| } |
| else |
| { |
| int ucSize = updateColumns.size(); |
| ResultColumn resultColumn; |
| String columnName; |
| |
| for (int index = 0; index < ucSize; index++) |
| { |
| columnName = updateColumns.get(index); |
| |
| resultColumn = getResultColumn(columnName); |
| if (SanityManager.DEBUG) |
| { |
| if (resultColumn == null && !dealingWithSelectResultColumnList) |
| { |
| SanityManager.THROWASSERT("No result column found with name " + |
| columnName); |
| } |
| } |
| //Following if means the column specified in FOR UPDATE clause is not |
| //part of the select list |
| if (resultColumn == null && dealingWithSelectResultColumnList) |
| continue; |
| resultColumn.markUpdatableByCursor(); |
| } |
| } |
| } |
| |
| /** |
| * Mark as updatable all the columns in this result column list |
| * that match the columns in the given update column list |
| * |
| * @param updateColumns A list representing the columns |
| * to be updated. |
| */ |
| void markUpdatableByCursor(List<String> updateColumns) |
| { |
| commonCodeForUpdatableByCursor(updateColumns, false); |
| } |
| |
| /** |
| * Returns true if the given column position is for a column that will |
| * be or could be updated by the positioned update of a cursor. |
| * |
| * @param columnPosition The position of the column in question |
| * |
| * @return true if the column is updatable |
| */ |
| boolean updatableByCursor(int columnPosition) |
| { |
| return getResultColumn(columnPosition).updatableByCursor(); |
| } |
| |
| |
| /** |
| * Return whether or not this RCL can be flattened out of a tree. |
| * It can only be flattened if the expressions are all cloneable. |
| * |
| * @return boolean Whether or not this RCL can be flattened out of a tree. |
| */ |
| boolean isCloneable() |
| { |
| boolean retcode = true; |
| for (ResultColumn rc : this) |
| { |
| if (! rc.getExpression().isCloneable()) |
| { |
| retcode = false; |
| break; |
| } |
| } |
| |
| return retcode; |
| } |
| |
| /** |
| * Remap all ColumnReferences in this tree to be clones of the |
| * underlying expression. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void remapColumnReferencesToExpressions() throws StandardException |
| { |
| for (ResultColumn rc : this) |
| { |
| // The expression may be null if this column is an identity |
| // column generated always. If the expression is not null, it |
| // is a ColumnReference; we call through to the ColumnReference |
| // to give it a chance to remap itself from the outer query |
| // node to this one. |
| if (rc.getExpression() != null) |
| rc.setExpression( |
| rc.getExpression().remapColumnReferencesToExpressions()); |
| } |
| } |
| |
| /* |
| ** Indicate that the conglomerate is an index, so we need to generate a |
| ** RowLocation as the last column of the result set. |
| ** |
| ** @param cid The conglomerate id of the index |
| */ |
| void setIndexRow(long cid, boolean forUpdate) |
| { |
| indexRow = true; |
| conglomerateId = cid; |
| this.forUpdate = forUpdate; |
| } |
| |
| /* Debugging methods */ |
| |
| /** |
| * Verify that all ResultColumns and their expressions have type information |
| * and that the type information between the respective RCs and |
| * expressions matches. |
| * |
| * @return boolean Whether or not the type information is consistent |
| */ |
| boolean hasConsistentTypeInfo() throws StandardException |
| { |
| boolean isConsistent = true; |
| |
| if (SanityManager.DEBUG) |
| { |
| for (ResultColumn rc : this) |
| { |
| ValueNode expr = rc.getExpression(); |
| DataTypeDescriptor rcDTS = rc.getTypeServices(); |
| DataTypeDescriptor exDTS = expr.getTypeServices(); |
| |
| if (rcDTS == null || exDTS == null) |
| { |
| isConsistent = false; |
| break; |
| } |
| |
| if (rcDTS.getClass().getName() != |
| exDTS.getClass().getName()) |
| { |
| isConsistent = false; |
| break; |
| } |
| } |
| } |
| |
| return isConsistent; |
| } |
| |
| /** |
| * Return whether or not this RCL contains an AllResultColumn. |
| * This is useful when dealing with SELECT * views which |
| * reference tables that may have had columns added to them via |
| * ALTER TABLE since the view was created. |
| * |
| * @return Whether or not this RCL contains an AllResultColumn. |
| */ |
| boolean containsAllResultColumn() |
| { |
| boolean containsAllResultColumn = false; |
| |
| int size = size(); |
| for (int index = 0; index < size; index++) |
| { |
| if (elementAt(index) instanceof AllResultColumn) |
| { |
| containsAllResultColumn = true; |
| break; |
| } |
| } |
| |
| return containsAllResultColumn; |
| } |
| |
| /** |
| * Count the number of RCs in the list that are referenced. |
| * |
| * @return The number of RCs in the list that are referenced. |
| */ |
| int countReferencedColumns() |
| { |
| int numReferenced = 0; |
| |
| for (ResultColumn rc : this) |
| { |
| if (rc.isReferenced()) |
| { |
| numReferenced++; |
| } |
| } |
| return numReferenced; |
| } |
| |
| /** |
| * Record the column ids of the referenced columns in the specified array. |
| * |
| * @param idArray int[] for column ids |
| * @param basis 0 (for 0-based ids) or 1 (for 1-based ids) |
| */ |
| void recordColumnReferences(int[] idArray, int basis) |
| { |
| int currArrayElement = 0; |
| int size = size(); |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn rc = elementAt(index); |
| |
| if (rc.isReferenced()) |
| { |
| idArray[currArrayElement++] = index + basis; |
| } |
| } |
| } |
| |
| /** |
| * Get the position of first result column with the given name. |
| * |
| * @param name Name of the column |
| * @param basis 0 (for 0-based ids) or 1 (for 1-based ids) |
| */ |
| int getPosition( String name, int basis ) |
| { |
| int size = size(); |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn rc = elementAt(index); |
| |
| if ( name.equals( rc.getName() ) ) |
| { |
| return index + basis; |
| } |
| } |
| |
| return -1; |
| } |
| |
| /** |
| * Record the top level ColumnReferences in the specified array |
| * and table map |
| * This is useful when checking for uniqueness conditions. |
| * NOTE: All top level CRs assumed to be from the same table. |
| * The size of the array is expected to be the # of columns |
| * in the table of interest + 1, so we use 1-base column #s. |
| * |
| * @param colArray1 boolean[] for columns |
| * @param tableColMap JBitSet[] for tables |
| * @param tableNumber Table number of column references |
| */ |
| void recordColumnReferences(boolean[] colArray1, JBitSet[] tableColMap, |
| int tableNumber) |
| { |
| for (ResultColumn rc : this) |
| { |
| int columnNumber; |
| |
| if (! (rc.getExpression() instanceof ColumnReference)) |
| { |
| continue; |
| } |
| |
| columnNumber = ((ColumnReference) rc.getExpression()).getColumnNumber(); |
| colArray1[columnNumber] = true; |
| tableColMap[tableNumber].set(columnNumber); |
| } |
| } |
| |
| /** |
| * Return whether or not all of the RCs in the list whose |
| * expressions are ColumnReferences are |
| * from the same table. One place this |
| * is useful for distinct elimination based on the existence |
| * of a uniqueness condition. |
| * |
| * @return -1 if all of the top level CRs in the RCL |
| * are not ColumnReferences from the same table, |
| * else the tableNumber |
| */ |
| int allTopCRsFromSameTable() |
| { |
| int tableNumber = -1; |
| |
| for (ResultColumn rc : this) |
| { |
| ValueNode vn = rc.getExpression(); |
| if (! (vn instanceof ColumnReference)) |
| { |
| continue; |
| } |
| |
| // Remember the tableNumber from the first CR |
| ColumnReference cr = (ColumnReference) vn; |
| if (tableNumber == -1) |
| { |
| tableNumber = cr.getTableNumber(); |
| } |
| else if (tableNumber != cr.getTableNumber()) |
| { |
| return -1; |
| } |
| } |
| return tableNumber; |
| } |
| |
| /** |
| * Clear the column references from the RCL. (Restore RCL back to a state |
| * where none of the RCs are marked as referenced.) |
| */ |
| void clearColumnReferences() |
| { |
| for (ResultColumn rc : this) |
| { |
| if (rc.isReferenced()) |
| { |
| rc.setUnreferenced(); |
| } |
| } |
| } |
| |
| /** |
| * Copy the referenced RCs from this list to the supplied target list. |
| * |
| * @param targetList The list to copy to |
| */ |
| void copyReferencedColumnsToNewList(ResultColumnList targetList) |
| { |
| for (ResultColumn rc : this) |
| { |
| if (rc.isReferenced()) |
| { |
| targetList.addElement(rc); |
| } |
| } |
| } |
| |
| /** |
| * Copy the RCs from this list to the supplied target list. |
| * |
| * @param targetList The list to copy to, |
| * @param copyList 1 based bitMap we copy columns associated with set bits. |
| */ |
| void copyColumnsToNewList( |
| ResultColumnList targetList, FormatableBitSet copyList) |
| { |
| for (ResultColumn rc : this) |
| { |
| if (copyList.isSet(rc.getColumnPosition())) |
| { |
| targetList.addElement(rc); |
| } |
| } |
| } |
| |
| |
| /** |
| * Get a FormatableBitSet of the columns referenced in this rcl |
| * |
| * @return the FormatableBitSet |
| */ |
| FormatableBitSet getColumnReferenceMap() |
| { |
| FormatableBitSet colMap = new FormatableBitSet(size()); |
| int size = size(); |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn rc = elementAt(index); |
| if (rc.isReferenced()) |
| { |
| colMap.set(index); |
| } |
| } |
| return colMap; |
| } |
| |
| /** |
| * Or in any isReferenced booleans from the virtual column chain. That is the isReferenced bits on each |
| * ResultColumn on the list will be set if the ResultColumn is referenced or if any VirtualColumnNode in its |
| * expression chain refers to a referenced column. |
| */ |
| void pullVirtualIsReferenced() |
| { |
| for (ResultColumn rc : this) |
| { |
| rc.pullVirtualIsReferenced(); |
| } |
| } // end of pullVirtualIsReferenced |
| |
| void clearTableNames() |
| { |
| for (ResultColumn rc : this) |
| { |
| rc.clearTableName(); |
| } |
| } |
| |
| /** |
| * Set the value of whether or not a count mismatch is allowed between |
| * this RCL, as a derived column list, and an underlying RCL. This is allowed |
| * for SELECT * views when an underlying table has had columns added to it |
| * via ALTER TABLE. |
| * |
| * @param allowed Whether or not a mismatch is allowed. |
| */ |
| protected void setCountMismatchAllowed(boolean allowed) |
| { |
| countMismatchAllowed = allowed; |
| } |
| |
| /** |
| * Return whether or not a count mismatch is allowed between this RCL, |
| * as a derived column list, and an underlying RCL. This is allowed |
| * for SELECT * views when an underlying table has had columns added to it |
| * via ALTER TABLE. |
| * |
| * return Whether or not a mismatch is allowed. |
| */ |
| |
| protected boolean getCountMismatchAllowed() |
| { |
| return countMismatchAllowed; |
| } |
| |
| /** |
| * Get the size of all the columns added |
| * together. Does <B>NOT</B> include the |
| * column overhead that the store requires. |
| * Also, will be a very rough estimate for |
| * user types. |
| * |
| * @return the size |
| */ |
| int getTotalColumnSize() |
| { |
| int colSize = 0; |
| |
| for (ResultColumn rc : this) |
| { |
| colSize += rc.getMaximumColumnSize(); |
| } |
| return colSize; |
| } |
| |
| /** |
| * Generate an RCL to match the contents of a ResultSetMetaData. |
| * This is useful when dealing with VTIs. |
| * |
| * @param rsmd The ResultSetMetaData. |
| * @param tableName The TableName for the BCNs. |
| * @param javaClassName The name of the VTI |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void createListFromResultSetMetaData(ResultSetMetaData rsmd, |
| TableName tableName, |
| String javaClassName) |
| throws StandardException |
| { |
| try |
| { |
| // JDBC columns #s are 1-based |
| // Check to make sure # of columns >= 1 |
| int numColumns = rsmd.getColumnCount(); |
| |
| if (numColumns <= 0) |
| { |
| throw StandardException.newException(SQLState.LANG_INVALID_V_T_I_COLUMN_COUNT, |
| javaClassName, String.valueOf(numColumns)); |
| } |
| |
| for (int index = 1; index <= numColumns; index++) |
| { |
| boolean nullableResult = |
| (rsmd.isNullable(index) != ResultSetMetaData.columnNoNulls); |
| |
| TypeId cti; |
| |
| int jdbcColumnType = rsmd.getColumnType(index); |
| |
| switch (jdbcColumnType) { |
| case Types.JAVA_OBJECT: |
| case Types.OTHER: |
| { |
| cti = TypeId.getUserDefinedTypeId( |
| rsmd.getColumnTypeName(index)); |
| break; |
| } |
| default: |
| { |
| cti = TypeId.getBuiltInTypeId(jdbcColumnType); |
| break; |
| } |
| } |
| |
| // Handle the case where a VTI returns a bad column type |
| if (cti == null) |
| { |
| throw StandardException.newException(SQLState.LANG_BAD_J_D_B_C_TYPE_INFO, Integer.toString(index)); |
| } |
| |
| // Get the maximum byte storage for this column |
| int maxWidth; |
| |
| /* Get maximum byte storage from rsmd for variable |
| * width types, set it to MAXINT for the long types, |
| * otherwise get it from the TypeId |
| */ |
| if (cti.variableLength()) |
| { |
| maxWidth = rsmd.getColumnDisplaySize(index); |
| } |
| else if (jdbcColumnType == Types.LONGVARCHAR || |
| jdbcColumnType == Types.LONGVARBINARY) |
| { |
| maxWidth = Integer.MAX_VALUE; |
| } |
| else |
| { |
| maxWidth = 0; |
| } |
| |
| int precision = cti.isDecimalTypeId() ? rsmd.getPrecision(index) : 0; |
| int scale = cti.isDecimalTypeId() ? rsmd.getScale(index) : 0; |
| DataTypeDescriptor dts = new DataTypeDescriptor(cti, |
| precision, |
| scale, |
| nullableResult, |
| maxWidth); |
| addColumn( tableName, rsmd.getColumnName(index), dts ); |
| } |
| } |
| catch (Throwable t) |
| { |
| if (t instanceof StandardException) |
| { |
| throw (StandardException) t; |
| } |
| else |
| { |
| throw StandardException.unexpectedUserException(t); |
| } |
| } |
| } |
| |
| /** |
| * Add a column to the list given a table name, column name, and data type. |
| * Return the just-added column. |
| * |
| */ |
| public ResultColumn addColumn( TableName tableName, String columnName, DataTypeDescriptor dts ) |
| throws StandardException |
| { |
| ValueNode bcn = new BaseColumnNode(columnName, |
| tableName, |
| dts, |
| getContextManager()); |
| ResultColumn rc = |
| new ResultColumn(columnName, bcn, getContextManager()); |
| rc.setType(dts); |
| addResultColumn(rc); |
| |
| return rc; |
| } |
| |
| /** |
| * Add an RC to the end of the list for the RID from an index. |
| * NOTE: RC.expression is a CurrentRowLocationNode. This was previously only used |
| * for non-select DML. We test for this node when generating the holder above |
| * and generate the expected code. (We really should create yet another new node |
| * type with its own code generation.) |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void addRCForRID() |
| throws StandardException |
| { |
| ResultColumn rowLocationColumn; |
| CurrentRowLocationNode rowLocationNode; |
| |
| /* Generate the RowLocation column */ |
| rowLocationNode = new CurrentRowLocationNode(getContextManager()); |
| rowLocationColumn = |
| new ResultColumn("", rowLocationNode, getContextManager()); |
| rowLocationColumn.markGenerated(); |
| |
| /* Append to the ResultColumnList */ |
| addResultColumn(rowLocationColumn); |
| } |
| |
| /** |
| * Walk the list and mark all RCs as unreferenced. This is useful |
| * when recalculating which RCs are referenced at what level like |
| * when deciding which columns need to be returned from a non-matching |
| * index scan (as opposed to those returned from the base table). |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void markAllUnreferenced() |
| throws StandardException |
| { |
| for (ResultColumn rc : this) |
| { |
| rc.setUnreferenced(); |
| } |
| } |
| |
| /** |
| * Determine if all of the RC.expressions are columns in the source result set. |
| * This is useful for determining if we need to do reflection |
| * at execution time. |
| * |
| * @param sourceRS The source ResultSet. |
| * |
| * @return Whether or not all of the RC.expressions are columns in the source result set. |
| */ |
| boolean allExpressionsAreColumns(ResultSetNode sourceRS) |
| { |
| for (ResultColumn rc : this) |
| { |
| //DERBY-4631 |
| //Following if condition if true means that the |
| // ResultColumn is a join column for a RIGHT OUTER |
| // JOIN with USING/NATURAL clause. At execution |
| // time, a join column's value should be determined |
| // by generated code which is equivalent to |
| // COALESCE(leftTableJoinColumn,rightTableJoinColumn). |
| // By returning false here, we allow Derby to generate |
| // code for functionality equivalent to COALESCE to |
| // determine join column's value. |
| if (rc.isRightOuterJoinUsingClause()) |
| return false; |
| |
| ValueNode expr = rc.getExpression(); |
| |
| if (! (expr instanceof VirtualColumnNode) && |
| ! (expr instanceof ColumnReference)) |
| { |
| return false; |
| } |
| |
| /* If the expression is a VirtualColumnNode, make sure that the column |
| * is coming from the source result set, ie, that it is not a correlated |
| * column. |
| */ |
| if (expr instanceof VirtualColumnNode) |
| { |
| VirtualColumnNode vcn = (VirtualColumnNode) expr; |
| if (vcn.getSourceResultSet() != sourceRS) |
| { |
| vcn.setCorrelated(); |
| return false; |
| } |
| } |
| |
| /* Make sure this is not a correlated CR */ |
| if (expr instanceof ColumnReference) |
| { |
| ColumnReference cr = (ColumnReference) expr; |
| if (cr.getCorrelated()) |
| { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Map the source columns to these columns. Build an array to represent the mapping. |
| * For each RC, if the expression is simply a VCN or a CR then set the array element to be |
| * the virtual column number of the source RC. Otherwise, set the array element to |
| * -1. |
| * This is useful for determining if we need to do reflection |
| * at execution time. |
| * <p/> |
| * Also build an array of boolean for columns that point to the same virtual |
| * column and have types that are streamable to be able to determine if |
| * cloning is needed at execution time. |
| * |
| * @return Array representiong mapping of RCs to source RCs. |
| */ |
| ColumnMapping mapSourceColumns() |
| { |
| int[] mapArray = new int[size()]; |
| boolean[] cloneMap = new boolean[size()]; |
| |
| ResultColumn resultColumn; |
| |
| // key: virtual column number, value: index |
| Map<Integer,Integer> seenMap = new HashMap<Integer,Integer>(); |
| |
| int size = size(); |
| |
| for (int index = 0; index < size; index++) |
| { |
| resultColumn = elementAt(index); |
| if (resultColumn.getExpression() instanceof VirtualColumnNode) |
| { |
| VirtualColumnNode vcn = (VirtualColumnNode) resultColumn.getExpression(); |
| |
| // Can't deal with correlated VCNs |
| if (vcn.getCorrelated()) |
| { |
| mapArray[index] = -1; |
| } |
| else |
| { |
| // Virtual column #s are 1-based |
| ResultColumn rc = vcn.getSourceColumn(); |
| updateArrays(mapArray, cloneMap, seenMap, rc, index); |
| |
| } |
| } |
| else if (resultColumn.isRightOuterJoinUsingClause()) |
| { |
| mapArray[index] = -1; |
| } |
| else if (resultColumn.getExpression() instanceof ColumnReference) |
| { |
| ColumnReference cr = (ColumnReference) resultColumn.getExpression(); |
| |
| // Can't deal with correlated CRs |
| if (cr.getCorrelated()) |
| { |
| mapArray[index] = -1; |
| } |
| else |
| { |
| // Virtual column #s are 1-based |
| ResultColumn rc = cr.getSource(); |
| |
| updateArrays(mapArray, cloneMap, seenMap, rc, index); |
| } |
| } |
| else |
| { |
| mapArray[index] = -1; |
| } |
| } |
| |
| ColumnMapping result = new ColumnMapping(mapArray, cloneMap); |
| return result; |
| } |
| |
| /** Set the nullability of every ResultColumn in this list |
| * @throws StandardException */ |
| void setNullability(boolean nullability) throws StandardException |
| { |
| for (ResultColumn rc : this) |
| { |
| rc.setNullability(nullability); |
| } |
| } |
| |
| /** |
| * Generate a FormatableBitSet representing the columns that are referenced in this RCL. |
| * The caller decides if they want this FormatableBitSet if every RC is referenced. |
| * |
| * @param positionedUpdate Whether or not the scan that the RCL |
| * belongs to is for update w/o a column list |
| * @param always Whether or not caller always wants a non-null FormatableBitSet if |
| * all RCs are referenced. |
| * @param onlyBCNs If true, only set bit if expression is a BaseColumnNode, |
| * otherwise set bit for all referenced RCs. |
| * |
| * @return The FormatableBitSet representing the referenced RCs. |
| */ |
| |
| FormatableBitSet getReferencedFormatableBitSet(boolean positionedUpdate, boolean always, boolean onlyBCNs) |
| { |
| int index; |
| int colsAdded = 0; |
| int size = size(); |
| |
| FormatableBitSet newReferencedCols = new FormatableBitSet(size); |
| |
| /* |
| ** For an updatable cursor, we need |
| ** all columns. |
| */ |
| if (positionedUpdate) |
| { |
| if (always) |
| { |
| /* Set all bits in the bit map */ |
| for (index = 0; index < size; index++) |
| { |
| newReferencedCols.set(index); |
| } |
| |
| return newReferencedCols; |
| } |
| else |
| { |
| return null; |
| } |
| } |
| |
| for (index = 0; index < size; index++) |
| { |
| ResultColumn oldCol = elementAt(index); |
| if (oldCol.isReferenced()) |
| { |
| /* Skip RCs whose expression is not a BCN |
| * when requested to do so. |
| */ |
| if (onlyBCNs && ! (oldCol.getExpression() instanceof BaseColumnNode)) |
| { |
| boolean skipable = |
| ( ! (oldCol.getExpression() instanceof BaseColumnNode) ) && |
| ( ! (oldCol.getExpression() instanceof CurrentRowLocationNode) ); |
| if ( skipable ) |
| { |
| continue; |
| } |
| } |
| newReferencedCols.set(index); |
| colsAdded++; |
| } |
| } |
| |
| /* Return the FormatableBitSet if not all RCs are referenced or if |
| * the caller always wants the FormatableBitSet returned. |
| */ |
| if (colsAdded != index || always) |
| { |
| return newReferencedCols; |
| } |
| else |
| { |
| return null; |
| } |
| } |
| |
| /** |
| * Create a new, compacted RCL based on the referenced RCs |
| * in this list. If the RCL being compacted is for an |
| * updatable scan, then we simply return this. |
| * |
| * The caller tells us whether or not they want a new list |
| * if there is no compaction because all RCs are referenced. |
| * This is useful in the case where the caller needs a new |
| * RCL for existing RCs so that it can augment the new list. |
| * |
| * @param positionedUpdate Whether or not the scan that the RCL |
| * belongs to is for update w/o a column list |
| * @param always Whether or not caller always wants a new RCL |
| * |
| * @return The compacted RCL if compaction occurred, otherwise return this RCL. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| ResultColumnList compactColumns(boolean positionedUpdate, boolean always) |
| throws StandardException |
| { |
| int index; |
| int colsAdded = 0; |
| |
| /* |
| ** For an updatable cursor, we need |
| ** all columns. |
| */ |
| if (positionedUpdate) |
| { |
| return this; |
| } |
| |
| ResultColumnList newCols = new ResultColumnList(getContextManager()); |
| |
| int size = size(); |
| for (index = 0; index < size; index++) |
| { |
| ResultColumn oldCol = elementAt(index); |
| if (oldCol.isReferenced()) |
| { |
| newCols.addResultColumn(oldCol); |
| colsAdded++; |
| } |
| } |
| |
| /* Return new RCL if we found unreferenced columns or if |
| * the caller always wants a new list. |
| */ |
| if (colsAdded != index || always) |
| { |
| return newCols; |
| } |
| else |
| { |
| return this; |
| } |
| } |
| |
| /** |
| * Remove the columns which are join columns (in the |
| * joinColumns RCL) from this list. This is useful |
| * for a JOIN with a USING clause. |
| * |
| * @param joinColumns The list of join columns |
| */ |
| void removeJoinColumns(ResultColumnList joinColumns) |
| { |
| for (ResultColumn joinRC : joinColumns) |
| { |
| String columnName = joinRC.getName(); |
| |
| // columnName should always be non-null |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(columnName != null, |
| "columnName should be non-null"); |
| } |
| |
| ResultColumn rightRC = getResultColumn(columnName); |
| |
| // Remove the RC from this list. |
| if (rightRC != null) |
| { |
| removeElement(rightRC); |
| } |
| } |
| } |
| |
| /** |
| * Get the join columns from this list. |
| * This is useful for a join with a USING clause. |
| * (ANSI specifies that the join columns appear 1st.) |
| * |
| * @param joinColumns A list of the join columns. |
| * |
| * @return A list of the join columns from this list |
| */ |
| ResultColumnList getJoinColumns(ResultColumnList joinColumns) |
| throws StandardException |
| { |
| ResultColumnList newRCL = new ResultColumnList(getContextManager()); |
| |
| /* Find all of the join columns and put them 1st on the |
| * new RCL. |
| */ |
| for (ResultColumn joinRC : joinColumns) |
| { |
| String columnName = joinRC.getName(); |
| |
| // columnName should always be non-null |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(columnName != null, |
| "columnName should be non-null"); |
| } |
| |
| ResultColumn xferRC = getResultColumn(columnName); |
| |
| if (xferRC == null) { |
| throw StandardException.newException( |
| SQLState.LANG_COLUMN_NOT_FOUND, columnName); |
| } |
| |
| // Add the RC to the new list. |
| newRCL.addElement(xferRC); |
| } |
| return newRCL; |
| } |
| |
| /** |
| * Reset the virtual column ids for all of the |
| * underlying RCs. (Virtual column ids are 1-based.) |
| */ |
| void resetVirtualColumnIds() |
| { |
| int size = size(); |
| |
| for (int index = 0; index < size; index++) |
| { |
| /* ResultColumns are 1-based */ |
| elementAt(index).setVirtualColumnId(index + 1); |
| } |
| } |
| |
| /** |
| * Return whether or not the same result row can be used for all |
| * rows returned by the associated ResultSet. This is possible |
| * if all entries in the list are constants or AggregateNodes. |
| * |
| * @return Whether or not the same result row can be used for all |
| * rows returned by the associated ResultSet. |
| */ |
| boolean reusableResult() |
| { |
| for (ResultColumn rc : this) |
| { |
| if ((rc.getExpression() instanceof ConstantNode) || |
| (rc.getExpression() instanceof AggregateNode)) |
| { |
| continue; |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Get an array of column positions (1-based) for all the columns |
| * in this RCL. Assumes that all the columns are in the passed-in |
| * table |
| * |
| * @return the array of strings |
| * |
| * @exception throws StandardException on error |
| */ |
| int[] getColumnPositions( TableDescriptor td ) |
| throws StandardException |
| { |
| int size = size(); |
| int[] myPositions = new int[ size ]; |
| String columnName; |
| ColumnDescriptor cd; |
| |
| for ( int index = 0; index < size; index++ ) |
| { |
| ResultColumn resultColumn = elementAt( index ); |
| columnName = resultColumn.getName(); |
| cd = td.getColumnDescriptor( columnName ); |
| |
| if ( cd == null ) |
| { |
| throw StandardException.newException |
| ( SQLState.LANG_COLUMN_NOT_FOUND_IN_TABLE, columnName, td.getQualifiedName() ); |
| } |
| |
| myPositions[ index ] = cd.getPosition(); |
| } |
| |
| return myPositions; |
| } |
| |
| /** |
| * Get an array of strings for all the columns |
| * in this RCL. |
| * |
| * @return the array of strings |
| */ |
| String[] getColumnNames() |
| { |
| String strings[] = new String[size()]; |
| |
| int size = size(); |
| |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn resultColumn = elementAt(index); |
| strings[index] = resultColumn.getName(); |
| } |
| return strings; |
| } |
| |
| /** |
| * Replace any DEFAULTs with the associated tree for the default if |
| * allowed, or flag. |
| * |
| * @param ttd The TableDescriptor for the target table. |
| * @param tcl The RCL for the target table. |
| * @param allowDefaults true if allowed |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void replaceOrForbidDefaults(TableDescriptor ttd, |
| ResultColumnList tcl, |
| boolean allowDefaults) |
| throws StandardException |
| { |
| int size = size(); |
| |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn rc = elementAt(index); |
| |
| if (rc.isDefaultColumn()) |
| { |
| if (!allowDefaults) { |
| throw StandardException.newException( |
| SQLState.LANG_INVALID_USE_OF_DEFAULT); |
| } |
| |
| // DefaultNode defaultNode = (DefaultNode) rc.getExpression(); |
| // Get ColumnDescriptor by name or by position? |
| ColumnDescriptor cd = null; |
| if (tcl == null) |
| { |
| cd = ttd.getColumnDescriptor(index + 1); |
| } |
| else if (index < tcl.size()) |
| { |
| ResultColumn trc = tcl.elementAt(index); |
| cd = ttd.getColumnDescriptor(trc.getName()); |
| } |
| |
| // Too many RCs if no ColumnDescriptor |
| if (cd == null) |
| { |
| throw StandardException.newException(SQLState.LANG_TOO_MANY_RESULT_COLUMNS, |
| ttd.getQualifiedName()); |
| } |
| |
| if (cd.isAutoincrement()) |
| { |
| rc.setAutoincrementGenerated(); |
| } // end of if () |
| |
| DefaultInfoImpl defaultInfo = (DefaultInfoImpl) cd.getDefaultInfo(); |
| |
| // |
| // For generated columns, we don't have enough context at this |
| // point to bind the generation clause (the default) and |
| // unfortunately that step occurs very soon after defaults are substituted in. The |
| // parsing and binding of generation clauses happens considerably |
| // later on. At this juncture, we can be patient and just plug |
| // in a NULL literal as a placeholder. For generated columns, |
| // the generation clause tree is plugged in in DMLModStatementNode.parseAndBindGenerationClauses(). |
| // |
| if ( (defaultInfo != null) && !defaultInfo.isGeneratedColumn() ) |
| { |
| setDefault( rc, cd, defaultInfo ); |
| } |
| else |
| { |
| rc.setExpression( |
| new UntypedNullConstantNode(getContextManager())); |
| rc.setWasDefaultColumn( true ); |
| } |
| rc.setDefaultColumn(false); |
| } |
| } |
| } |
| |
| /** Set the default in a ResultColumn */ |
| void setDefault( ResultColumn rc, ColumnDescriptor cd, DefaultInfoImpl defaultInfo ) |
| throws StandardException |
| { |
| /* Query is dependent on the DefaultDescriptor */ |
| DefaultDescriptor defaultDescriptor = cd.getDefaultDescriptor(getDataDictionary()); |
| getCompilerContext().createDependency(defaultDescriptor); |
| |
| rc.setExpression |
| ( |
| DefaultNode.parseDefault |
| ( |
| defaultInfo.getDefaultText(), |
| getLanguageConnectionContext(), |
| getCompilerContext() |
| ) |
| ); |
| } |
| |
| /** |
| * Walk the RCL and check for DEFAULTs. DEFAULTs |
| * are invalid at the time that this method is called, |
| * so we throw an exception if found. |
| * NOTE: The grammar allows: |
| * VALUES DEFAULT; |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void checkForInvalidDefaults() |
| throws StandardException |
| { |
| for (ResultColumn rc : this) |
| { |
| if (rc.isAutoincrementGenerated()) |
| continue; |
| |
| if (rc.isDefaultColumn()) |
| { |
| throw StandardException.newException(SQLState.LANG_INVALID_USE_OF_DEFAULT); |
| } |
| } |
| } |
| |
| /** |
| * Verify that all of the RCs in this list are comparable. |
| * |
| * @exception StandardException Thrown on error |
| */ |
| void verifyAllOrderable() |
| throws StandardException |
| { |
| for (ResultColumn rc : this) |
| { |
| rc.verifyOrderable(); |
| } |
| } |
| |
| /** |
| * Build this ResultColumnList from a table description and |
| * an array of column IDs. |
| * |
| * @param table describes the table |
| * @param columnIDs column positions in that table (1-based) |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public void populate |
| ( |
| TableDescriptor table, |
| int[] columnIDs |
| ) |
| throws StandardException |
| { |
| if ( columnIDs == null ) { return; } |
| |
| int count = columnIDs.length; |
| String columnName; |
| int columnPosition; |
| ResultColumn rc; |
| |
| for ( int i = 0; i < count; i++ ) |
| { |
| columnPosition = columnIDs[ i ]; |
| columnName = table.getColumnDescriptor( columnPosition ).getColumnName(); |
| |
| rc = makeColumnFromName( columnName ); |
| |
| addResultColumn( rc ); |
| } |
| |
| } |
| |
| private ResultColumn makeColumnFromName( String columnName ) |
| throws StandardException |
| { |
| return new ResultColumn(columnName, null, getContextManager()); |
| } |
| |
| private ResultColumn makeColumnReferenceFromName |
| ( |
| TableName tableName, |
| String columnName |
| ) |
| throws StandardException |
| { |
| ContextManager cm = getContextManager(); |
| |
| ResultColumn rc = new ResultColumn( |
| columnName, |
| new ColumnReference(columnName, tableName, cm), |
| cm |
| ); |
| |
| return rc; |
| } |
| |
| /** |
| * check if any autoincrement or generated columns exist in the result column list. |
| * called from insert or update where you cannot insert/update the value |
| * of a generated or autoincrement column. |
| * |
| * @exception StandardException If the column is an ai column |
| */ |
| void forbidOverrides(ResultColumnList sourceRSRCL) |
| throws StandardException |
| { |
| forbidOverrides( sourceRSRCL, false ); |
| } |
| |
| /** |
| * check if any autoincrement or generated columns exist in the result column list. |
| * called from insert or update where you cannot insert/update the value |
| * of a generated or autoincrement column. |
| * |
| * @exception StandardException If the column is an ai column |
| */ |
| void forbidOverrides(ResultColumnList sourceRSRCL, boolean defaultsWereReplaced ) |
| throws StandardException |
| { |
| int size = size(); |
| |
| for (int index = 0; index < size; index++) |
| { |
| ResultColumn rc = elementAt(index); |
| ResultColumn sourceRC = |
| (sourceRSRCL == null) ? null : sourceRSRCL.elementAt(index); |
| ColumnDescriptor cd = rc.getTableColumnDescriptor(); |
| |
| if ( (cd != null) && cd.hasGenerationClause() ) |
| { |
| if ( !defaultsWereReplaced && (sourceRC != null) && !sourceRC.hasGenerationClause() && !sourceRC.wasDefaultColumn() ) |
| { |
| throw StandardException.newException(SQLState.LANG_CANT_OVERRIDE_GENERATION_CLAUSE, rc.getName()); |
| } |
| |
| if ( sourceRC != null ) { sourceRC.setColumnDescriptor(cd.getTableDescriptor(), cd); } |
| } |
| |
| if ((cd != null) && (cd.isAutoincrement())) |
| { |
| if ( |
| ( (sourceRC != null) && (sourceRC.isAutoincrementGenerated()) ) || |
| ( cd.isAutoincAlways() && defaultsWereReplaced ) |
| ) |
| { |
| sourceRC.setColumnDescriptor(cd.getTableDescriptor(), cd); |
| |
| }else{ |
| if(cd.isAutoincAlways()) |
| throw StandardException.newException(SQLState.LANG_AI_CANNOT_MODIFY_AI, |
| rc.getName()); |
| } |
| } |
| } |
| } |
| |
| void incOrderBySelect() |
| { |
| orderBySelect++; |
| } |
| |
| private void decOrderBySelect() |
| { |
| orderBySelect--; |
| } |
| |
| int getOrderBySelect() |
| { |
| return orderBySelect; |
| } |
| |
| public void copyOrderBySelect( ResultColumnList src) |
| { |
| orderBySelect = src.orderBySelect; |
| } |
| |
| /* **** |
| * Take note of the size of this RCL _before_ we start |
| * processing/binding it. This is so that, at bind time, |
| * we can tell if any columns in the RCL were added |
| * internally by us (i.e. they were not specified by the |
| * user and thus will not be returned to the user). |
| */ |
| protected void markInitialSize() { |
| initialListSize = size(); |
| } |
| |
| private int numGeneratedColumns() |
| { |
| int numGenerated = 0; |
| int sz = size(); |
| boolean inVisibleRange = false; |
| for (int i = sz - 1; i >= 0; i--) |
| { |
| ResultColumn rc = elementAt(i); |
| if (rc.isGenerated()) |
| { |
| if (SanityManager.DEBUG) { |
| // We expect the generated columns to always be at the end of the list. |
| if (inVisibleRange) |
| SanityManager.THROWASSERT("Encountered generated column in expected visible range at rcl[" + i +"]"); |
| } |
| numGenerated++; |
| } else { |
| // We are counting down, so as soon as we see one visible column, the rest should be th same |
| inVisibleRange = true; |
| } |
| } |
| return numGenerated; |
| } |
| |
| /** |
| * @return the number of generated columns in this RCL. |
| */ |
| int numGeneratedColumnsForGroupBy() |
| { |
| int numGenerated = 0; |
| int sz = size(); |
| for (int i = sz - 1; i >= 0; i--) { |
| ResultColumn rc = elementAt(i); |
| if (rc.isGenerated() && rc.isGroupingColumn()) |
| { |
| numGenerated++; |
| } |
| } |
| return numGenerated; |
| } |
| |
| /** |
| * Remove any generated columns from this RCL. |
| */ |
| void removeGeneratedGroupingColumns() |
| { |
| int sz = size(); |
| for (int i = sz - 1; i >= 0; i--) |
| { |
| ResultColumn rc = elementAt(i); |
| if (rc.isGenerated() && rc.isGroupingColumn()) |
| { |
| removeElementAt(i); |
| } |
| } |
| } |
| |
| /** |
| * @return the number of columns that will be visible during execution. |
| * During compilation we can add columns for a group by/order by but these |
| * to an RCL but these are projected out during query execution. |
| */ |
| int visibleSize() |
| { |
| return size() - orderBySelect - numGeneratedColumns(); |
| } |
| |
| /** |
| * Convert this object to a String. See comments in QueryTreeNode.java |
| * for how this should be done for tree printing. |
| * |
| * @return This object as a String |
| */ |
| @Override |
| public String toString() |
| { |
| if (SanityManager.DEBUG) { |
| return "indexRow: " + indexRow + "\n" + |
| "orderBySelect: " + orderBySelect + "\n" + |
| (indexRow ? "conglomerateId: " + conglomerateId + "\n" |
| : "") + |
| super.toString(); |
| } else { |
| return ""; |
| } |
| } |
| |
| |
| private static boolean streamableType(ResultColumn rc) { |
| DataTypeDescriptor dtd = rc.getType(); |
| TypeId s = TypeId.getBuiltInTypeId(dtd.getTypeName()); |
| |
| if (s != null) { |
| return s.streamStorable(); |
| } else { |
| return false; |
| } |
| } |
| |
| |
| private static void updateArrays(int[] mapArray, |
| boolean[] cloneMap, |
| Map<Integer,Integer> seenMap, |
| ResultColumn rc, |
| int index) { |
| |
| int vcId = rc.getVirtualColumnId(); |
| |
| mapArray[index] = vcId; |
| |
| if (streamableType(rc)) { |
| Integer seenIndex = seenMap.get(Integer.valueOf(vcId)); |
| |
| if (seenIndex != null) { |
| // We have already mapped this column at index |
| // seenIndex, so mark occurence 2..n for cloning. |
| cloneMap[index] = true; |
| } else { |
| seenMap.put(vcId, index); |
| } |
| } |
| } |
| |
| |
| static class ColumnMapping { |
| |
| final int[] mapArray; |
| final boolean[] cloneMap; |
| |
| private ColumnMapping(int[] mapArray, boolean[] cloneMap) { |
| this.mapArray = mapArray; |
| this.cloneMap = cloneMap; |
| } |
| } |
| } |