--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.compile.CursorNode\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.sql.compile;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Vector;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.services.compiler.MethodBuilder;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import org.apache.derby.iapi.sql.ResultColumnDescriptor;\r
+import org.apache.derby.iapi.sql.compile.C_NodeTypes;\r
+import org.apache.derby.iapi.sql.dictionary.DataDictionary;\r
+import org.apache.derby.iapi.sql.dictionary.TableDescriptor;\r
+import org.apache.derby.impl.sql.CursorInfo;\r
+import org.apache.derby.impl.sql.CursorTableReference;\r
+\r
+/**\r
+ * A CursorNode represents a result set that can be returned to a client.\r
+ * A cursor can be a named cursor created by the DECLARE CURSOR statement,\r
+ * or it can be an unnamed cursor associated with a SELECT statement (more\r
+ * precisely, a table expression that returns rows to the client). In the\r
+ * latter case, the cursor does not have a name.\r
+ *\r
+ */\r
+\r
+public class CursorNode extends DMLStatementNode\r
+{\r
+ public final static int UNSPECIFIED = 0;\r
+ public final static int READ_ONLY = 1;\r
+ public final static int UPDATE = 2;\r
+\r
+ private String name;\r
+ private OrderByList orderByList;\r
+ private String statementType;\r
+ private int updateMode;\r
+ private boolean needTarget;\r
+\r
+ /**\r
+ ** There can only be a list of updatable columns when FOR UPDATE\r
+ ** is specified as part of the cursor specification.\r
+ */\r
+ private Vector updatableColumns;\r
+ private FromTable updateTable;\r
+ private ResultColumnDescriptor[] targetColumnDescriptors;\r
+\r
+ //If cursor references session schema tables, save the list of those table names in savedObjects in compiler context\r
+ //Following is the position of the session table names list in savedObjects in compiler context\r
+ //At generate time, we save this position in activation for easy access to session table names list from compiler context\r
+ private int indexOfSessionTableNamesInSavedObjects = -1;\r
+\r
+ /**\r
+ * Initializer for a CursorNode\r
+ *\r
+ * @param statementType Type of statement (SELECT, UPDATE, INSERT)\r
+ * @param resultSet A ResultSetNode specifying the result set for\r
+ * the cursor\r
+ * @param name The name of the cursor, null if no name\r
+ * @param orderByList The order by list for the cursor, null if no\r
+ * order by list\r
+ * @param updateMode The user-specified update mode for the cursor,\r
+ * for example, CursorNode.READ_ONLY\r
+ * @param updatableColumns The list of updatable columns specified by\r
+ * the user in the FOR UPDATE clause, null if no\r
+ * updatable columns specified. May only be\r
+ * provided if the updateMode parameter is\r
+ * CursorNode.UPDATE.\r
+ */\r
+\r
+ public void init(\r
+ Object statementType,\r
+ Object resultSet,\r
+ Object name,\r
+ Object orderByList,\r
+ Object updateMode,\r
+ Object updatableColumns)\r
+ {\r
+ init(resultSet);\r
+ this.name = (String) name;\r
+ this.statementType = (String) statementType;\r
+ this.orderByList = (OrderByList) orderByList;\r
+\r
+ this.updateMode = ((Integer) updateMode).intValue();\r
+ this.updatableColumns = (Vector) updatableColumns;\r
+\r
+ /*\r
+ ** This is a sanity check and not an error since the parser\r
+ ** controls setting updatableColumns and updateMode.\r
+ */\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(this.updatableColumns == null ||\r
+ this.updatableColumns.size() == 0 || this.updateMode == UPDATE,\r
+ "Can only have explicit updatable columns if update mode is UPDATE");\r
+ }\r
+\r
+ /**\r
+ * Convert this object to a String. See comments in QueryTreeNode.java\r
+ * for how this should be done for tree printing.\r
+ *\r
+ * @return This object as a String\r
+ */\r
+\r
+ public String toString()\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ return "name: " + name + "\n" +\r
+ "updateMode: " + updateModeString(updateMode) + "\n" +\r
+ super.toString();\r
+ }\r
+ else\r
+ {\r
+ return "";\r
+ }\r
+ }\r
+\r
+ public String statementToString()\r
+ {\r
+ return statementType;\r
+ }\r
+\r
+ /**\r
+ * Support routine for translating an updateMode identifier to a String\r
+ *\r
+ * @param updateMode An updateMode identifier\r
+ *\r
+ * @return A String representing the update mode.\r
+ */\r
+\r
+ private static String updateModeString(int updateMode)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ switch (updateMode)\r
+ {\r
+ case UNSPECIFIED:\r
+ return "UNSPECIFIED (" + UNSPECIFIED + ")";\r
+\r
+ case READ_ONLY:\r
+ return "READ_ONLY (" + READ_ONLY + ")";\r
+\r
+ case UPDATE:\r
+ return "UPDATE (" + UPDATE + ")";\r
+\r
+ default:\r
+ return "UNKNOWN VALUE (" + updateMode + ")";\r
+ }\r
+ }\r
+ else\r
+ {\r
+ return "";\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Prints the sub-nodes of this object. See QueryTreeNode.java for\r
+ * how tree printing is supposed to work.\r
+ *\r
+ * @param depth The depth of this node in the tree\r
+ */\r
+\r
+ public void printSubNodes(int depth)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ super.printSubNodes(depth);\r
+\r
+ printLabel(depth, "orderByList: ");\r
+ if (orderByList != null)\r
+ orderByList.treePrint(depth + 1);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Bind this CursorNode. This means looking up tables and columns and\r
+ * getting their types, and figuring out the result types of all\r
+ * expressions, as well as doing view resolution, permissions checking,\r
+ * etc. It also includes determining whether an UNSPECIFIED cursor\r
+ * is updatable or not, and verifying that an UPDATE cursor actually is.\r
+ *\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public void bindStatement() throws StandardException\r
+ {\r
+ DataDictionary dataDictionary;\r
+\r
+ dataDictionary = getDataDictionary();\r
+\r
+ // This is how we handle queries like: SELECT A FROM T ORDER BY B.\r
+ // We pull up the order by columns (if they don't appear in the SELECT\r
+ // LIST) and let the bind() do the job. Note that the pullup is done\r
+ // before the bind() and we may avoid pulling up ORDERBY columns that\r
+ // would otherwise be avoided, e.g., "SELECT * FROM T ORDER BY B".\r
+ // Pulled-up ORDERBY columns that are duplicates (like the above "SELECT\r
+ // *" query will be removed in bindOrderByColumns().\r
+ // Finally, given that extra columns may be added to the SELECT list, we\r
+ // inject a ProjectRestrictNode so that only the user-specified columns\r
+ // will be returned (see genProjectRestrict() in SelectNode.java).\r
+ if (orderByList != null)\r
+ {\r
+ orderByList.pullUpOrderByColumns(resultSet);\r
+ }\r
+\r
+ getCompilerContext().pushCurrentPrivType(getPrivType());\r
+ try {\r
+ FromList fromList = (FromList) getNodeFactory().getNode(\r
+ C_NodeTypes.FROM_LIST,\r
+ getNodeFactory().doJoinOrderOptimization(),\r
+ getContextManager());\r
+\r
+ /* Check for ? parameters directly under the ResultColums */\r
+ resultSet.rejectParameters();\r
+\r
+ super.bind(dataDictionary);\r
+\r
+ // bind the query expression\r
+ resultSet.bindResultColumns(fromList);\r
+\r
+ // this rejects any untyped nulls in the select list\r
+ // pass in null to indicate that we don't have any\r
+ // types for this node\r
+ resultSet.bindUntypedNullsToResultColumns(null);\r
+\r
+ // Reject any XML values in the select list; JDBC doesn't\r
+ // define how we bind these out, so we don't allow it.\r
+ resultSet.rejectXMLValues();\r
+\r
+ /* Verify that all underlying ResultSets reclaimed their FromList */\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.ASSERT(fromList.size() == 0,\r
+ "fromList.size() is expected to be 0, not "\r
+ + fromList.size()\r
+ + " on return from RS.bindExpressions()");\r
+ }\r
+ }\r
+ finally\r
+ {\r
+ getCompilerContext().popCurrentPrivType();\r
+ }\r
+\r
+ // bind the order by\r
+ if (orderByList != null)\r
+ {\r
+ orderByList.bindOrderByColumns(resultSet);\r
+ }\r
+\r
+ // bind the updatability\r
+\r
+ // if it says it is updatable, verify it.\r
+ if (updateMode == UPDATE)\r
+ {\r
+ int checkedUpdateMode;\r
+\r
+ checkedUpdateMode = determineUpdateMode(dataDictionary);\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.DEBUG("DumpUpdateCheck","update mode is UPDATE ("+updateMode+") checked mode is "+checkedUpdateMode);\r
+ if (updateMode != checkedUpdateMode)\r
+ throw StandardException.newException(SQLState.LANG_STMT_NOT_UPDATABLE);\r
+ }\r
+\r
+ // if it doesn't know if it is updatable, determine it\r
+ if (updateMode == UNSPECIFIED)\r
+ {\r
+ // If the statement is opened with CONCUR_READ_ONLY, the upgrade mode is \r
+ // set to read only.\r
+ \r
+ // NOTE: THIS IS NOT COMPATIBLE WITH THE ISO/ANSI SQL STANDARD.\r
+\r
+ // According to the SQL-standard:\r
+ // If updatability is not specified, a SELECT * FROM T will be implicitely\r
+ // read only in the context of a cursor which is insensitive, scrollable or\r
+ // have an order by clause. Otherwise it is implicitely updatable.\r
+ \r
+ // In Derby, we make a SELECT * FROM T updatable if the concurrency mode is\r
+ // ResultSet.CONCUR_UPDATE. If we do make all SELECT * FROM T updatable\r
+ // by default, we cannot use an index on any single-table select, unless it\r
+ // was declared FOR READ ONLY. This would be pretty terrible, so we are\r
+ // breaking the ANSI rules.\r
+\r
+ if (getLanguageConnectionContext().getStatementContext().isForReadOnly()) {\r
+ updateMode = READ_ONLY;\r
+ } else {\r
+ updateMode = determineUpdateMode(dataDictionary);\r
+ }\r
+ \r
+ //if (SanityManager.DEBUG)\r
+ //SanityManager.DEBUG("DumpUpdateCheck","update mode is UNSPECIFIED ("+UNSPECIFIED+") checked mode is "+updateMode);\r
+ }\r
+ \r
+ if (updateMode == READ_ONLY) {\r
+ updatableColumns = null; // don't need them any more\r
+ }\r
+\r
+ // bind the update columns\r
+ if (updateMode == UPDATE)\r
+ {\r
+ bindUpdateColumns(updateTable);\r
+\r
+ // If the target table is a FromBaseTable, mark the updatable\r
+ // columns. (I can't think of a way that an updatable table\r
+ // could be anything but a FromBaseTable at this point, but\r
+ // it's better to be careful.\r
+ if (updateTable instanceof FromTable)\r
+ {\r
+ ((FromTable) updateTable).markUpdatableByCursor(updatableColumns);\r
+ //make sure that alongwith the FromTable, we keep other ResultSetLists\r
+ //in correct state too. ResultSetMetaData.isWritable looks at this to\r
+ //return the correct value.\r
+ resultSet.getResultColumns().markColumnsInSelectListUpdatableByCursor(\r
+ updatableColumns);\r
+ }\r
+ }\r
+\r
+ resultSet.renameGeneratedResultNames();\r
+\r
+ //need to look for SESSION tables only if global temporary tables declared for the connection\r
+ if (getLanguageConnectionContext().checkIfAnyDeclaredGlobalTempTablesForThisConnection())\r
+ {\r
+ //If this cursor has references to session schema tables, save the names of those tables into compiler context\r
+ //so they can be passed to execution phase.\r
+ ArrayList sessionSchemaTableNames = getSessionSchemaTableNamesForCursor();\r
+ if (sessionSchemaTableNames != null)\r
+ indexOfSessionTableNamesInSavedObjects = getCompilerContext().addSavedObject(sessionSchemaTableNames);\r
+ }\r
+\r
+ }\r
+\r
+ /**\r
+ * Return true if the node references SESSION schema tables (temporary or permanent)\r
+ *\r
+ * @return true if references SESSION schema tables, else false\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public boolean referencesSessionSchema()\r
+ throws StandardException\r
+ {\r
+ //If this node references a SESSION schema table, then return true. \r
+ return resultSet.referencesSessionSchema();\r
+ }\r
+\r
+ //Check if this cursor references any session schema tables. If so, pass those names to execution phase through savedObjects\r
+ //This list will be used to check if there are any holdable cursors referencing temporary tables at commit time.\r
+ //If yes, then the data in those temporary tables should be preserved even if they are declared with ON COMMIT DELETE ROWS option\r
+ protected ArrayList getSessionSchemaTableNamesForCursor()\r
+ throws StandardException\r
+ {\r
+ FromList fromList = resultSet.getFromList();\r
+ int fromListSize = fromList.size();\r
+ FromTable fromTable;\r
+ ArrayList sessionSchemaTableNames = null;\r
+\r
+ for( int i = 0; i < fromListSize; i++)\r
+ {\r
+ fromTable = (FromTable) fromList.elementAt(i);\r
+ if (fromTable instanceof FromBaseTable && isSessionSchema(fromTable.getTableDescriptor().getSchemaDescriptor()))\r
+ {\r
+ if (sessionSchemaTableNames == null)\r
+ sessionSchemaTableNames = new ArrayList();\r
+ sessionSchemaTableNames.add(fromTable.getTableName().getTableName());\r
+ }\r
+ }\r
+\r
+ return sessionSchemaTableNames;\r
+ }\r
+\r
+ /**\r
+ * Take a cursor and determine if it is UPDATE\r
+ * or READ_ONLY based on the shape of the cursor specification.\r
+ * <p>\r
+ * The following conditions make a cursor read only:\r
+ * <UL>\r
+ * <LI>if it says FOR READ ONLY\r
+ * <LI>if it says ORDER BY\r
+ * <LI>if its query specification is not read only. At present this\r
+ * is explicitly tested here, with these conditions. At some future\r
+ * point in time, this checking ought to be moved into the\r
+ * ResultSet nodes themselves. The conditions for a query spec.\r
+ * not to be read only include:\r
+ * <UL>\r
+ * <LI>if it has a set operation such as UNION or INTERSECT, i.e.\r
+ * does not have a single outermost SELECT\r
+ * <LI>if it does not have exactly 1 table in its FROM list;\r
+ * 0 tables would occur if we ever support a SELECT without a\r
+ * FROM e.g., for generating a row without an underlying table\r
+ * (like what we do for an INSERT of a VALUES list); >1 tables\r
+ * occurs when joins are in the tree.\r
+ * <LI>if the table in its FROM list is not a base table (REMIND\r
+ * when views/from subqueries are added, this should be relaxed to\r
+ * be that the table is not updatable)\r
+ * <LI>if it has a GROUP BY or HAVING (NOTE I am assuming that if\r
+ * and aggregate is detected in a SELECT w/o a GROUP BY, one\r
+ * has been added to show that the whole table is a group)\r
+ * <LI> NOTE that cursors are updatable even if none of the columns\r
+ * in the select are updatable -- what they care about is the\r
+ * updatability of the columns of the target table.\r
+ * </UL>\r
+ * </UL>\r
+ *\r
+ * @return the known update mode for the cursor.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ private int determineUpdateMode(DataDictionary dataDictionary)\r
+ throws StandardException\r
+ {\r
+ SelectNode selectNode;\r
+ FromList tables;\r
+ FromTable targetTable;\r
+\r
+ if (updateMode == READ_ONLY)\r
+ {\r
+ return READ_ONLY;\r
+ }\r
+\r
+ if (orderByList != null)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.DEBUG("DumpUpdateCheck","cursor has order by");\r
+ return READ_ONLY;\r
+ }\r
+\r
+ // get the ResultSet to tell us what it thinks it is\r
+ // and the target table\r
+ if (! resultSet.isUpdatableCursor(dataDictionary))\r
+ {\r
+ return READ_ONLY;\r
+ }\r
+\r
+ // The FOR UPDATE clause has two uses:\r
+ //\r
+ // for positioned cursor updates\r
+ //\r
+ // to change locking behaviour of the select\r
+ // to reduce deadlocks on subsequent updates\r
+ // in the same transaction.\r
+ //\r
+ // We now support this latter case, without requiring\r
+ // that the source of the rows be able to implement\r
+ // a positioned update.\r
+\r
+ updateTable = resultSet.getCursorTargetTable();\r
+\r
+ /* Tell the table that it is the cursor target */\r
+ if (updateTable.markAsCursorTargetTable()) {\r
+ /* Cursor is updatable - remember to generate the position code */\r
+ needTarget = true;\r
+\r
+ /* We must generate the target column list at bind time\r
+ * because the optimizer may transform the FromBaseTable from\r
+ * a table scan into an index scan.\r
+ */\r
+ genTargetResultColList();\r
+ }\r
+\r
+\r
+\r
+\r
+ return UPDATE;\r
+ }\r
+\r
+ /**\r
+ * Optimize a DML statement (which is the only type of statement that\r
+ * should need optimizing, I think). This method over-rides the one\r
+ * in QueryTreeNode.\r
+ *\r
+ * This method takes a bound tree, and returns an optimized tree.\r
+ * It annotates the bound tree rather than creating an entirely\r
+ * new tree.\r
+ *\r
+ * Throws an exception if the tree is not bound, or if the binding\r
+ * is out of date.\r
+ *\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public void optimizeStatement() throws StandardException\r
+ {\r
+ // Push the order by list down to the ResultSet\r
+ if (orderByList != null)\r
+ {\r
+ // If we have more than 1 ORDERBY columns, we may be able to\r
+ // remove duplicate columns, e.g., "ORDER BY 1, 1, 2".\r
+ if (orderByList.size() > 1)\r
+ {\r
+ orderByList.removeDupColumns();\r
+ }\r
+\r
+ resultSet.pushOrderByList(orderByList);\r
+ orderByList = null;\r
+ }\r
+ super.optimizeStatement();\r
+ }\r
+\r
+ /**\r
+ * Returns the type of activation this class\r
+ * generates.\r
+ * \r
+ * @return either (NEED_CURSOR_ACTIVATION\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ \r
+ int activationKind()\r
+ {\r
+ return NEED_CURSOR_ACTIVATION;\r
+ }\r
+\r
+ /**\r
+ * Do code generation for this CursorNode\r
+ *\r
+ * @param acb The ActivationClassBuilder for the class being built\r
+ * @param mb The method the generated code is to go into\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public void generate(ActivationClassBuilder acb,\r
+ MethodBuilder mb) throws StandardException\r
+ {\r
+ if (indexOfSessionTableNamesInSavedObjects != -1 ) //if this cursor references session schema tables, do following\r
+ {\r
+ MethodBuilder constructor = acb.getConstructor();\r
+ constructor.pushThis();\r
+ constructor.push(indexOfSessionTableNamesInSavedObjects);\r
+ constructor.putField(org.apache.derby.iapi.reference.ClassName.BaseActivation, "indexOfSessionTableNamesInSavedObjects", "int");\r
+ constructor.endStatement();\r
+ }\r
+\r
+ // generate the parameters\r
+ generateParameterValueSet(acb);\r
+\r
+ // tell the outermost result set that it is the outer\r
+ // result set of the statement.\r
+ resultSet.markStatementResultSet();\r
+\r
+ generateAuthorizeCheck(acb, mb,\r
+ org.apache.derby.iapi.sql.conn.Authorizer.SQL_SELECT_OP);\r
+\r
+ // this will generate an expression that will be a ResultSet\r
+ resultSet.generate(acb, mb);\r
+\r
+ /*\r
+ ** Generate the position code if this cursor is updatable. This\r
+ ** involves generating methods to get the cursor result set, and\r
+ ** the target result set (which is for the base row). Also,\r
+ ** generate code to store the cursor result set in a generated\r
+ ** field.\r
+ */\r
+ if (needTarget)\r
+ {\r
+ // PUSHCOMPILE - could be put into a single method\r
+ acb.rememberCursor(mb);\r
+ acb.addCursorPositionCode();\r
+ }\r
+ }\r
+\r
+ // class interface\r
+\r
+ public String getUpdateBaseTableName() \r
+ {\r
+ return (updateTable == null) ? null : updateTable.getBaseTableName();\r
+ }\r
+\r
+ public String getUpdateExposedTableName() \r
+ throws StandardException\r
+ {\r
+ return (updateTable == null) ? null : updateTable.getExposedName();\r
+ }\r
+\r
+ public String getUpdateSchemaName() \r
+ throws StandardException\r
+ {\r
+ //we need to use the base table for the schema name\r
+ return (updateTable == null) ? null : ((FromBaseTable)updateTable).getTableNameField().getSchemaName();\r
+ }\r
+\r
+ public int getUpdateMode()\r
+ {\r
+ return updateMode;\r
+ }\r
+\r
+ /**\r
+ * Return String[] of names from the FOR UPDATE OF List\r
+ *\r
+ * @return String[] of names from the FOR UPDATE OF list.\r
+ */\r
+ private String[] getUpdatableColumns()\r
+ {\r
+ return (updatableColumns == null) ?\r
+ (String[])null :\r
+ getUpdateColumnNames();\r
+ }\r
+\r
+ /**\r
+ Positioned update needs to know what the target result set\r
+ looks like. This is generated from the UpdateColumnList\r
+ available for the cursor, to describe the rows coming from\r
+ the target result set under the cursor. This result set contains\r
+ a superset of the updatable columns; the caller must verify that\r
+ only those listed in the FOR UPDATE clause are used.\r
+\r
+ @return a result column list containing a description of\r
+ the target table (this may contain non-updatable columns).\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ private ResultColumnDescriptor[] genTargetResultColList()\r
+ throws StandardException\r
+ {\r
+ ResultColumnList newList;\r
+\r
+ /*\r
+ updateTable holds the FromTable that is the target.\r
+ copy its ResultColumnList, making BaseColumn references\r
+ for use in the CurrentOfNode, which behaves as if it had\r
+ base columns for the statement it is in.\r
+\r
+ updateTable is null if the cursor is not updatable.\r
+ */\r
+ if (updateTable == null) return null;\r
+\r
+ if (targetColumnDescriptors != null) return targetColumnDescriptors;\r
+\r
+ newList = (ResultColumnList) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN_LIST,\r
+ getContextManager());\r
+ ResultColumnList rcl = updateTable.getResultColumns();\r
+ int rclSize = rcl.size();\r
+ for (int index = 0; index < rclSize; index++)\r
+ {\r
+ ResultColumn origCol, newCol;\r
+ ValueNode newNode;\r
+\r
+ origCol = (ResultColumn) rcl.elementAt(index);\r
+\r
+ // Build a ResultColumn/BaseColumnNode pair for the column\r
+ newNode = (ValueNode) getNodeFactory().getNode(\r
+ C_NodeTypes.BASE_COLUMN_NODE,\r
+ origCol.getName(),\r
+ makeTableName(origCol.getSchemaName(),\r
+ origCol.getTableName()), \r
+ origCol.getTypeServices(),\r
+ getContextManager());\r
+ newCol = (ResultColumn) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN,\r
+ origCol.columnDescriptor,\r
+ newNode,\r
+ getContextManager());\r
+\r
+ /* Build the ResultColumnList to return */\r
+ newList.addResultColumn(newCol);\r
+ }\r
+\r
+ // we save the result so we only do this once\r
+ targetColumnDescriptors = newList.makeResultDescriptors();\r
+ return targetColumnDescriptors;\r
+ }\r
+\r
+ /**\r
+ * Returns whether or not this Statement requires a set/clear savepoint\r
+ * around its execution. The following statement "types" do not require them:\r
+ * Cursor - unnecessary and won't work in a read only environment\r
+ * Xact - savepoint will get blown away underneath us during commit/rollback\r
+ *\r
+ * @return boolean Whether or not this Statement requires a set/clear savepoint\r
+ */\r
+ public boolean needsSavepoint()\r
+ {\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ * Get information about this cursor. For sps,\r
+ * this is info saved off of the original query\r
+ * tree (the one for the underlying query).\r
+ *\r
+ * @return the cursor info\r
+ * @exception StandardException thrown if generation fails\r
+ */\r
+ public Object getCursorInfo()\r
+ throws StandardException\r
+ {\r
+ if (!needTarget)\r
+ return null;\r
+\r
+ return new CursorInfo(updateMode,\r
+ new CursorTableReference(\r
+ getUpdateExposedTableName(),\r
+ getUpdateBaseTableName(),\r
+ getUpdateSchemaName()),\r
+ genTargetResultColList(),\r
+ getUpdatableColumns());\r
+ }\r
+\r
+ /**\r
+ Bind the update columns by their names to the target table\r
+ of the cursor specification.\r
+ Doesn't check for duplicates in the list, although it could...\r
+ REVISIT: If the list is empty, should it expand it out? at present,\r
+ it leaves it empty.\r
+ \r
+ @param targetTable The underlying target table \r
+ \r
+ @exception StandardException Thrown on error\r
+ */\r
+ private void bindUpdateColumns(FromTable targetTable)\r
+ throws StandardException \r
+ {\r
+ int size = updatableColumns.size();\r
+ TableDescriptor tableDescriptor;\r
+ String columnName;\r
+ ResultColumnList rcls = resultSet.getResultColumns();\r
+\r
+ for (int index = 0; index < size; index++)\r
+ {\r
+ columnName = (String) updatableColumns.elementAt(index);\r
+ tableDescriptor = targetTable.getTableDescriptor();\r
+ if ( tableDescriptor.getColumnDescriptor(columnName) == null)\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_COLUMN_NOT_FOUND, columnName);\r
+ }\r
+\r
+ ResultColumn rc;\r
+ //make sure that we are not using correlation names for updatable columns. \r
+ //eg select c11 as col1, 2, c13 as col3 from t1 for update of c11, c12\r
+ //In the eg above, correlation name for c11 will cause exception because Derby does not support correlation name for updatable columns\r
+ //But correlation name for c13 is ok because it is a read only column\r
+ for (int rclsIndex = 0; rclsIndex < rcls.size(); rclsIndex++) {//look through each column in the resultset for cursor\r
+ rc = ((ResultColumn) rcls.elementAt(rclsIndex));\r
+ if (rc.getSourceTableName() == null) //continue to look at the next column because this is derived column in the select list\r
+ continue;\r
+ if (rc.getExpression() != null && rc.getExpression().getColumnName().equals(columnName) && !rc.getName().equals(columnName)) {\r
+ throw StandardException.newException(SQLState.LANG_CORRELATION_NAME_FOR_UPDATABLE_COLUMN_DISALLOWED_IN_CURSOR, columnName);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Get an array of strings for each updatable column\r
+ * in this list.\r
+ *\r
+ * @return an array of strings\r
+ */\r
+ private String[] getUpdateColumnNames()\r
+ {\r
+ int size = updatableColumns.size();\r
+ if (size == 0)\r
+ {\r
+ return (String[])null;\r
+ }\r
+\r
+ String[] names = new String[size];\r
+\r
+ updatableColumns.copyInto(names);\r
+\r
+ return names;\r
+ }\r
+ \r
+ public String getXML()\r
+ {\r
+ return null;\r
+ }\r
+}\r