--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.compile.UpdateNode\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.sql.compile;\r
+\r
+import org.apache.derby.iapi.services.context.ContextManager;\r
+\r
+import org.apache.derby.iapi.services.loader.GeneratedMethod;\r
+\r
+import org.apache.derby.iapi.services.compiler.MethodBuilder;\r
+\r
+import org.apache.derby.impl.sql.compile.ActivationClassBuilder;\r
+import org.apache.derby.iapi.sql.conn.Authorizer;\r
+import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;\r
+import org.apache.derby.impl.sql.execute.FKInfo;\r
+import org.apache.derby.iapi.services.compiler.MethodBuilder;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.sql.compile.CompilerContext;\r
+import org.apache.derby.iapi.sql.compile.C_NodeTypes;\r
+import org.apache.derby.iapi.sql.compile.Visitable;\r
+import org.apache.derby.iapi.sql.compile.Visitor;\r
+\r
+import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;\r
+\r
+import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.ConstraintDescriptorList;\r
+import org.apache.derby.iapi.sql.dictionary.ConstraintDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.CheckConstraintDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.DataDictionary;\r
+import org.apache.derby.iapi.sql.dictionary.IndexRowGenerator;\r
+import org.apache.derby.iapi.sql.dictionary.TableDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.GenericDescriptorList;\r
+\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.sql.execute.ConstantAction;\r
+import org.apache.derby.iapi.sql.execute.CursorResultSet;\r
+import org.apache.derby.iapi.sql.execute.ExecPreparedStatement;\r
+import org.apache.derby.iapi.sql.execute.ExecRow;\r
+\r
+import org.apache.derby.iapi.sql.Activation;\r
+import org.apache.derby.iapi.sql.ResultSet;\r
+import org.apache.derby.iapi.sql.StatementType;\r
+\r
+import org.apache.derby.iapi.store.access.StaticCompiledOpenConglomInfo;\r
+import org.apache.derby.iapi.store.access.TransactionController;\r
+\r
+import org.apache.derby.vti.DeferModification;\r
+\r
+import org.apache.derby.iapi.services.io.FormatableBitSet;\r
+import org.apache.derby.iapi.reference.ClassName;\r
+\r
+import org.apache.derby.iapi.util.ReuseFactory;\r
+import org.apache.derby.iapi.services.classfile.VMOpcode;\r
+\r
+import java.lang.reflect.Modifier;\r
+import java.sql.SQLException;\r
+import java.util.Properties;\r
+import java.util.Vector;\r
+\r
+/**\r
+ * An UpdateNode represents an UPDATE statement. It is the top node of the\r
+ * query tree for that statement.\r
+ * For positioned update, there may be no from table specified.\r
+ * The from table will be derived from the cursor specification of\r
+ * the named cursor.\r
+ *\r
+ */\r
+\r
+public final class UpdateNode extends DMLModStatementNode\r
+{\r
+ //Note: These are public so they will be visible to\r
+ //the RepUpdateNode.\r
+ public int[] changedColumnIds;\r
+ public ExecRow emptyHeapRow;\r
+ public boolean deferred;\r
+ public ValueNode checkConstraints;\r
+ public FKInfo fkInfo;\r
+ \r
+ protected FromTable targetTable;\r
+ protected FormatableBitSet readColsBitSet;\r
+ protected boolean positionedUpdate;\r
+\r
+ /* Column name for the RowLocation in the ResultSet */\r
+ public static final String COLUMNNAME = "###RowLocationToUpdate";\r
+\r
+ /**\r
+ * Initializer for an UpdateNode.\r
+ *\r
+ * @param targetTableName The name of the table to update\r
+ * @param resultSet The ResultSet that will generate\r
+ * the rows to update from the given table\r
+ */\r
+\r
+ public void init(\r
+ Object targetTableName,\r
+ Object resultSet)\r
+ {\r
+ super.init(resultSet);\r
+ this.targetTableName = (TableName) targetTableName;\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 targetTableName.toString() + "\n" +\r
+ super.toString();\r
+ }\r
+ else\r
+ {\r
+ return "";\r
+ }\r
+ }\r
+\r
+ public String statementToString()\r
+ {\r
+ return "UPDATE";\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
+ if (targetTableName != null)\r
+ {\r
+ printLabel(depth, "targetTableName: ");\r
+ targetTableName.treePrint(depth + 1);\r
+ }\r
+\r
+ /* RESOLVE - need to print out targetTableDescriptor */\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Bind this UpdateNode. 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.\r
+ * <p>\r
+ * Binding an update will also massage the tree so that\r
+ * the ResultSetNode has a set of columns to contain the old row\r
+ * value, followed by a set of columns to contain the new row\r
+ * value, followed by a column to contain the RowLocation of the\r
+ * row to be updated.\r
+ *\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public void bindStatement() throws StandardException\r
+ {\r
+ // We just need select privilege on the expressions\r
+ getCompilerContext().pushCurrentPrivType( Authorizer.SELECT_PRIV);\r
+\r
+ FromList fromList = (FromList) getNodeFactory().getNode(\r
+ C_NodeTypes.FROM_LIST,\r
+ getNodeFactory().doJoinOrderOptimization(),\r
+ getContextManager());\r
+ ResultColumn rowLocationColumn = null;\r
+ ValueNode rowLocationNode = null;\r
+ TableName cursorTargetTableName = null;\r
+ CurrentOfNode currentOfNode = null;\r
+ FromList resultFromList;\r
+ ResultColumnList afterColumns = null;\r
+\r
+ DataDictionary dataDictionary = getDataDictionary();\r
+\r
+ // check if targetTable is a synonym\r
+ if (targetTableName != null)\r
+ {\r
+ TableName synonymTab = resolveTableToSynonym(this.targetTableName);\r
+ if (synonymTab != null)\r
+ {\r
+ this.synonymTableName = targetTableName;\r
+ this.targetTableName = synonymTab;\r
+ }\r
+ }\r
+\r
+ bindTables(dataDictionary);\r
+\r
+ // wait to bind named target table until the cursor\r
+ // binding is done, so that we can get it from the\r
+ // cursor if this is a positioned update.\r
+\r
+ // for positioned update, get the cursor's target table.\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT((resultSet!=null && resultSet instanceof SelectNode), \r
+ "Update must have a select result set");\r
+ }\r
+\r
+ SelectNode sel;\r
+ sel = (SelectNode)resultSet;\r
+ targetTable = (FromTable) sel.fromList.elementAt(0);\r
+\r
+ if (targetTable instanceof CurrentOfNode) \r
+ { \r
+ positionedUpdate = true;\r
+ currentOfNode = (CurrentOfNode) targetTable;\r
+ cursorTargetTableName = currentOfNode.getBaseCursorTargetTableName();\r
+\r
+ // instead of an assert, we might say the cursor is not updatable.\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(cursorTargetTableName != null);\r
+ }\r
+ }\r
+\r
+ if (targetTable instanceof FromVTI)\r
+ {\r
+ targetVTI = (FromVTI) targetTable;\r
+ targetVTI.setTarget();\r
+ }\r
+ else\r
+ {\r
+ // positioned update can leave off the target table.\r
+ // we get it from the cursor supplying the position.\r
+ if (targetTableName == null)\r
+ {\r
+ // verify we have current of\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(cursorTargetTableName!=null);\r
+\r
+ targetTableName = cursorTargetTableName;\r
+ }\r
+ // for positioned update, we need to verify that\r
+ // the named table is the same as the cursor's target.\r
+ else if (cursorTargetTableName != null)\r
+ {\r
+ // this match requires that the named table in the update\r
+ // be the same as a correlation name in the cursor.\r
+ if ( !targetTableName.equals(cursorTargetTableName))\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_CURSOR_UPDATE_MISMATCH, \r
+ targetTableName,\r
+ currentOfNode.getCursorName());\r
+ }\r
+ }\r
+ }\r
+ \r
+ // because we verified that the tables match\r
+ // and we already bound the cursor or the select,\r
+ // the table descriptor should always be found.\r
+ verifyTargetTable();\r
+ \r
+ /* OVERVIEW - We generate a new ResultColumn, CurrentRowLocation(), and\r
+ * prepend it to the beginning of the source ResultColumnList. This\r
+ * will tell us which row(s) to update at execution time. However,\r
+ * we must defer prepending this generated column until the other\r
+ * ResultColumns are bound since there will be no ColumnDescriptor\r
+ * for the generated column. Thus, the sequence of actions is:\r
+ *\r
+ * o Bind existing ResultColumnList (columns in SET clause)\r
+ * o If this is a positioned update with a FOR UPDATE OF list,\r
+ * then verify that all of the target columns are in the\r
+ * FOR UPDATE OF list.\r
+ * o Get the list of indexes that need to be updated.\r
+ * o Create a ResultColumnList of all the columns in the target\r
+ * table - this represents the old row.\r
+ * o If we don't know which columns are being updated, \r
+ * expand the original ResultColumnList to include all the\r
+ * columns in the target table, and sort it to be in the\r
+ * order of the columns in the target table. This represents\r
+ * the new row. Append it to the ResultColumnList representing\r
+ * the old row.\r
+ * o Construct the changedColumnIds array sorted by column position.\r
+ * o Generate the read column bit map and append any columns\r
+ * needed for index maint, etc.\r
+ * o Generate a new ResultColumn for CurrentRowLocation() and \r
+ * mark it as a generated column.\r
+ * o Append the new ResultColumn to the ResultColumnList\r
+ * (This must be done before binding the expressions, so\r
+ * that the proper type info gets propagated to the new \r
+ * ResultColumn.)\r
+ * o Bind the expressions.\r
+ * o Bind the generated ResultColumn.\r
+ */\r
+\r
+ /* Verify that all underlying ResultSets reclaimed their FromList */\r
+ if (SanityManager.DEBUG)\r
+ {\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
+ /*\r
+ ** The current result column list is the one supplied by the user.\r
+ ** Mark these columns as "updated", so we can tell later which\r
+ ** columns are really being updated, and which have been added\r
+ ** but are not really being updated.\r
+ */\r
+ resultSet.getResultColumns().markUpdated();\r
+\r
+ /* Prepend CurrentRowLocation() to the select's result column list. */\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT((resultSet.resultColumns != null), \r
+ "resultColumns is expected not to be null at bind time");\r
+\r
+ /*\r
+ ** Get the result FromTable, which should be the only table in the\r
+ ** from list.\r
+ */\r
+ resultFromList = resultSet.getFromList();\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(resultFromList.size() == 1,\r
+ "More than one table in result from list in an update.");\r
+\r
+ /* Normalize the SET clause's result column list for synonym */\r
+ if (synonymTableName != null)\r
+ normalizeSynonymColumns( resultSet.resultColumns, targetTable );\r
+ \r
+ /* Bind the original result columns by column name */\r
+ normalizeCorrelatedColumns( resultSet.resultColumns, targetTable );\r
+\r
+ getCompilerContext().pushCurrentPrivType(getPrivType()); // Update privilege\r
+ resultSet.bindResultColumns(targetTableDescriptor,\r
+ targetVTI,\r
+ resultSet.resultColumns, this,\r
+ fromList);\r
+ getCompilerContext().popCurrentPrivType();\r
+\r
+ LanguageConnectionContext lcc = getLanguageConnectionContext();\r
+ if (lcc.getAutoincrementUpdate() == false)\r
+ resultSet.getResultColumns().checkAutoincrement(null);\r
+\r
+ /*\r
+ ** Mark the columns in this UpdateNode's result column list as\r
+ ** updateable in the ResultColumnList of the table being updated.\r
+ ** only do this for FromBaseTables - if the result table is a\r
+ ** CurrentOfNode, it already knows what columns in its cursor\r
+ ** are updateable.\r
+ */\r
+ boolean allColumns = false;\r
+ if (targetTable instanceof FromBaseTable)\r
+ {\r
+ ((FromBaseTable) targetTable).markUpdated(\r
+ resultSet.getResultColumns());\r
+ }\r
+ else if (targetTable instanceof FromVTI)\r
+ {\r
+ resultColumnList = resultSet.getResultColumns();\r
+ }\r
+ else\r
+ {\r
+ /*\r
+ ** Positioned update: WHERE CURRENT OF\r
+ */\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(currentOfNode != null, "currentOfNode is null");\r
+ }\r
+\r
+ ExecPreparedStatement cursorStmt = currentOfNode.getCursorStatement();\r
+ String[] ucl = cursorStmt.getUpdateColumns();\r
+\r
+ /*\r
+ ** If there is no update column list, we need to build\r
+ ** out the result column list to have all columns.\r
+ */\r
+ if (ucl == null || (ucl.length == 0))\r
+ {\r
+ /*\r
+ ** Get the resultColumnList representing ALL of the columns in the \r
+ ** base table. This is the "before" portion of the result row.\r
+ */\r
+ getResultColumnList();\r
+\r
+ /*\r
+ ** Add the "after" portion of the result row. This is the update\r
+ ** list augmented to include every column in the target table.\r
+ ** Those columns that are not being updated are set to themselves.\r
+ ** The expanded list will be in the order of the columns in the base\r
+ ** table.\r
+ */\r
+ afterColumns = resultSet.getResultColumns().expandToAll(\r
+ targetTableDescriptor,\r
+ targetTable.getTableName());\r
+ \r
+ /*\r
+ ** Need to get all indexes here since we aren't calling\r
+ ** getReadMap().\r
+ */\r
+ getAffectedIndexes(targetTableDescriptor, \r
+ (ResultColumnList)null, (FormatableBitSet)null); \r
+ allColumns = true;\r
+ }\r
+ else\r
+ {\r
+ /* Check the updatability */\r
+ resultSet.getResultColumns().checkColumnUpdateability(ucl,\r
+ currentOfNode.getCursorName());\r
+ }\r
+ }\r
+\r
+ changedColumnIds = getChangedColumnIds(resultSet.getResultColumns());\r
+\r
+ /*\r
+ ** We need to add in all the columns that are needed\r
+ ** by the constraints on this table. \r
+ */\r
+ if (!allColumns && targetVTI == null)\r
+ {\r
+ getCompilerContext().pushCurrentPrivType( Authorizer.NULL_PRIV);\r
+ try\r
+ {\r
+ readColsBitSet = new FormatableBitSet();\r
+ FromBaseTable fbt = getResultColumnList(resultSet.getResultColumns());\r
+ afterColumns = resultSet.getResultColumns().copyListAndObjects();\r
+\r
+ readColsBitSet = getReadMap(dataDictionary, \r
+ targetTableDescriptor, \r
+ afterColumns);\r
+\r
+ afterColumns = fbt.addColsToList(afterColumns, readColsBitSet);\r
+ resultColumnList = fbt.addColsToList(resultColumnList, readColsBitSet);\r
+\r
+ /*\r
+ ** If all bits are set, then behave as if we chose all\r
+ ** in the first place\r
+ */\r
+ int i = 1;\r
+ int size = targetTableDescriptor.getMaxColumnID();\r
+ for (; i <= size; i++)\r
+ {\r
+ if (!readColsBitSet.get(i))\r
+ {\r
+ break;\r
+ }\r
+ }\r
+\r
+ if (i > size)\r
+ {\r
+ readColsBitSet = null;\r
+ allColumns = true;\r
+ } \r
+ }\r
+ finally\r
+ {\r
+ getCompilerContext().popCurrentPrivType();\r
+ }\r
+ }\r
+\r
+ if (targetVTI == null)\r
+ {\r
+ /*\r
+ ** Construct an empty heap row for use in our constant action.\r
+ */\r
+ emptyHeapRow = targetTableDescriptor.getEmptyExecRow();\r
+\r
+ /* Append the list of "after" columns to the list of "before" columns,\r
+ * preserving the afterColumns list. (Necessary for binding\r
+ * check constraints.)\r
+ */\r
+ resultColumnList.appendResultColumns(afterColumns, false);\r
+\r
+ /* Generate the RowLocation column */\r
+ rowLocationNode = (CurrentRowLocationNode) getNodeFactory().getNode(\r
+ C_NodeTypes.CURRENT_ROW_LOCATION_NODE,\r
+ getContextManager());\r
+ }\r
+ else\r
+ {\r
+ rowLocationNode = (NumericConstantNode) getNodeFactory().getNode(\r
+ C_NodeTypes.INT_CONSTANT_NODE,\r
+ ReuseFactory.getInteger( 0),\r
+ getContextManager());\r
+ }\r
+ \r
+ rowLocationColumn =\r
+ (ResultColumn) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN,\r
+ COLUMNNAME,\r
+ rowLocationNode,\r
+ getContextManager());\r
+ rowLocationColumn.markGenerated();\r
+\r
+ /* Append to the ResultColumnList */\r
+ resultColumnList.addResultColumn(rowLocationColumn);\r
+\r
+ /*\r
+ * The last thing that we do to the generated RCL is to clear\r
+ * the table name out from each RC. See comment on \r
+ * checkTableNameAndScrubResultColumns().\r
+ */\r
+ checkTableNameAndScrubResultColumns(resultColumnList);\r
+\r
+ /* Set the new result column list in the result set */\r
+ resultSet.setResultColumns(resultColumnList);\r
+\r
+ /* Bind the expressions */\r
+ getCompilerContext().pushCurrentPrivType(getPrivType()); // Update privilege\r
+ super.bindExpressions();\r
+ getCompilerContext().popCurrentPrivType();\r
+\r
+ /* Bind untyped nulls directly under the result columns */\r
+ resultSet.\r
+ getResultColumns().\r
+ bindUntypedNullsToResultColumns(resultColumnList);\r
+\r
+ if (null != rowLocationColumn)\r
+ {\r
+ /* Bind the new ResultColumn */\r
+ rowLocationColumn.bindResultColumnToExpression();\r
+ }\r
+\r
+ resultColumnList.checkStorableExpressions();\r
+\r
+ /* Insert a NormalizeResultSetNode above the source if the source\r
+ * and target column types and lengths do not match.\r
+ */\r
+ if (! resultColumnList.columnTypesAndLengthsMatch())\r
+ {\r
+ resultSet = resultSet.genNormalizeResultSetNode(resultSet, true);\r
+ resultColumnList.copyTypesAndLengthsToSource(resultSet.getResultColumns());\r
+ \r
+ if (hasCheckConstraints(dataDictionary, targetTableDescriptor))\r
+ {\r
+ /* Get and bind all check constraints on the columns\r
+ * being updated. We want to bind the check constraints against\r
+ * the after columns. We need to bind against the portion of the\r
+ * resultColumns in the new NormalizeResultSet that point to \r
+ * afterColumns. Create an RCL composed of just those RCs in\r
+ * order to bind the check constraints.\r
+ */\r
+ int afterColumnsSize = afterColumns.size();\r
+ afterColumns = (ResultColumnList) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN_LIST,\r
+ getContextManager());\r
+ ResultColumnList normalizedRCs = resultSet.getResultColumns();\r
+ for (int index = 0; index < afterColumnsSize; index++)\r
+ {\r
+ afterColumns.addElement(normalizedRCs.elementAt(index + afterColumnsSize));\r
+ }\r
+ }\r
+ }\r
+\r
+ if( null != targetVTI)\r
+ {\r
+ deferred = VTIDeferModPolicy.deferIt( DeferModification.UPDATE_STATEMENT,\r
+ targetVTI,\r
+ resultColumnList.getColumnNames(),\r
+ sel.getWhereClause());\r
+ }\r
+ else // not VTI\r
+ {\r
+ /* we always include triggers in core language */\r
+ boolean hasTriggers = (getAllRelevantTriggers(dataDictionary, targetTableDescriptor, \r
+ changedColumnIds, true).size() > 0);\r
+\r
+ /* Get and bind all constraints on the columns being updated */\r
+ checkConstraints = bindConstraints( dataDictionary,\r
+ getNodeFactory(),\r
+ targetTableDescriptor,\r
+ null,\r
+ hasTriggers ? resultColumnList : afterColumns,\r
+ changedColumnIds,\r
+ readColsBitSet,\r
+ false,\r
+ true); /* we always include triggers in core language */\r
+\r
+ /* If the target table is also a source table, then\r
+ * the update will have to be in deferred mode\r
+ * For updates, this means that the target table appears in a\r
+ * subquery. Also, self referencing foreign keys are\r
+ * deferred. And triggers cause an update to be deferred.\r
+ */\r
+ if (resultSet.subqueryReferencesTarget(\r
+ targetTableDescriptor.getName(), true) ||\r
+ requiresDeferredProcessing())\r
+ {\r
+ deferred = true;\r
+ }\r
+ }\r
+\r
+ getCompilerContext().popCurrentPrivType(); \r
+\r
+ } // end of bind()\r
+\r
+ int getPrivType()\r
+ {\r
+ return Authorizer.UPDATE_PRIV;\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
+\r
+ /**\r
+ * Compile constants that Execution will use\r
+ *\r
+ * @exception StandardException Thrown on failure\r
+ */\r
+ public ConstantAction makeConstantAction() throws StandardException\r
+ {\r
+ /*\r
+ ** Updates are also deferred if they update a column in the index\r
+ ** used to scan the table being updated.\r
+ */\r
+ if (! deferred )\r
+ {\r
+ ConglomerateDescriptor updateCD =\r
+ targetTable.\r
+ getTrulyTheBestAccessPath().\r
+ getConglomerateDescriptor();\r
+\r
+ if (updateCD != null && updateCD.isIndex())\r
+ {\r
+ int [] baseColumns =\r
+ updateCD.getIndexDescriptor().baseColumnPositions();\r
+\r
+ if (resultSet.\r
+ getResultColumns().\r
+ updateOverlaps(baseColumns))\r
+ {\r
+ deferred = true;\r
+ }\r
+ }\r
+ }\r
+\r
+ if( null == targetTableDescriptor)\r
+ {\r
+ /* Return constant action for VTI\r
+ * NOTE: ConstantAction responsible for preserving instantiated\r
+ * VTIs for in-memory queries and for only preserving VTIs\r
+ * that implement Serializable for SPSs.\r
+ */\r
+ return getGenericConstantActionFactory().getUpdatableVTIConstantAction( DeferModification.UPDATE_STATEMENT,\r
+ deferred, changedColumnIds);\r
+ }\r
+\r
+ int lockMode = resultSet.updateTargetLockMode();\r
+ long heapConglomId = targetTableDescriptor.getHeapConglomerateId();\r
+ TransactionController tc = \r
+ getLanguageConnectionContext().getTransactionCompile();\r
+ StaticCompiledOpenConglomInfo[] indexSCOCIs = \r
+ new StaticCompiledOpenConglomInfo[indexConglomerateNumbers.length];\r
+\r
+ for (int index = 0; index < indexSCOCIs.length; index++)\r
+ {\r
+ indexSCOCIs[index] = tc.getStaticCompiledConglomInfo(indexConglomerateNumbers[index]);\r
+ }\r
+\r
+ /*\r
+ ** Do table locking if the table's lock granularity is\r
+ ** set to table.\r
+ */\r
+ if (targetTableDescriptor.getLockGranularity() == TableDescriptor.TABLE_LOCK_GRANULARITY)\r
+ {\r
+ lockMode = TransactionController.MODE_TABLE;\r
+ }\r
+\r
+\r
+ return getGenericConstantActionFactory().getUpdateConstantAction\r
+ ( heapConglomId,\r
+ targetTableDescriptor.getTableType(),\r
+ tc.getStaticCompiledConglomInfo(heapConglomId),\r
+ indicesToMaintain,\r
+ indexConglomerateNumbers,\r
+ indexSCOCIs,\r
+ indexNames,\r
+ emptyHeapRow,\r
+ deferred,\r
+ targetTableDescriptor.getUUID(),\r
+ lockMode,\r
+ false,\r
+ changedColumnIds, null, null, \r
+ getFKInfo(),\r
+ getTriggerInfo(),\r
+ (readColsBitSet == null) ? (FormatableBitSet)null : new FormatableBitSet(readColsBitSet),\r
+ getReadColMap(targetTableDescriptor.getNumberOfColumns(),readColsBitSet),\r
+ resultColumnList.getStreamStorableColIds(targetTableDescriptor.getNumberOfColumns()),\r
+ (readColsBitSet == null) ? \r
+ targetTableDescriptor.getNumberOfColumns() :\r
+ readColsBitSet.getNumBitsSet(), \r
+ positionedUpdate,\r
+ resultSet.isOneRowResultSet()\r
+ );\r
+ }\r
+\r
+ /**\r
+ * Updates are deferred if they update a column in the index\r
+ * used to scan the table being updated.\r
+ */\r
+ protected void setDeferredForUpdateOfIndexColumn()\r
+ {\r
+ /* Don't bother checking if we're already deferred */\r
+ if (! deferred )\r
+ {\r
+ /* Get the conglomerate descriptor for the target table */\r
+ ConglomerateDescriptor updateCD =\r
+ targetTable.\r
+ getTrulyTheBestAccessPath().\r
+ getConglomerateDescriptor();\r
+\r
+ /* If it an index? */\r
+ if (updateCD != null && updateCD.isIndex())\r
+ {\r
+ int [] baseColumns =\r
+ updateCD.getIndexDescriptor().baseColumnPositions();\r
+\r
+ /* Are any of the index columns updated? */\r
+ if (resultSet.\r
+ getResultColumns().\r
+ updateOverlaps(baseColumns))\r
+ {\r
+ deferred = true;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Code generation for update.\r
+ * The generated code will contain:\r
+ * o A static member for the (xxx)ResultSet with the RowLocations and\r
+ * new update values\r
+ * o The static member will be assigned the appropriate ResultSet within\r
+ * the nested calls to get the ResultSets. (The appropriate cast to the\r
+ * (xxx)ResultSet will be generated.)\r
+ * o The CurrentRowLocation() in SelectNode's select list will generate\r
+ * a new method for returning the RowLocation as well as a call to\r
+ * that method when generating the (xxx)ResultSet.\r
+ *\r
+ * @param acb The ActivationClassBuilder for the class being built\r
+ * @param mb The method for the execute() method to be built\r
+ *\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void generate(ActivationClassBuilder acb,\r
+ MethodBuilder mb)\r
+ throws StandardException\r
+ {\r
+ // If the DML is on the temporary table, generate the code to\r
+ // mark temporary table as modified in the current UOW. After\r
+ // DERBY-827 this must be done in execute() since\r
+ // fillResultSet() will only be called once.\r
+ generateCodeForTemporaryTable(acb, acb.getExecuteMethod());\r
+\r
+ /* generate the parameters */\r
+ if(!isDependentTable)\r
+ generateParameterValueSet(acb);\r
+\r
+\r
+ /* Create the static declaration for the scan ResultSet which generates the\r
+ * RowLocations to be updated\r
+ * RESOLVE - Need to deal with the type of the static member.\r
+ */\r
+ acb.newFieldDeclaration(Modifier.PRIVATE, \r
+ ClassName.CursorResultSet, \r
+ acb.newRowLocationScanResultSetName());\r
+\r
+ /*\r
+ ** Generate the update result set, giving it either the original\r
+ ** source or the normalize result set, the constant action.\r
+ */\r
+\r
+ acb.pushGetResultSetFactoryExpression(mb);\r
+ resultSet.generate(acb, mb); // arg 1\r
+\r
+ if( null != targetVTI)\r
+ {\r
+ targetVTI.assignCostEstimate(resultSet.getNewCostEstimate());\r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, "getUpdateVTIResultSet", ClassName.ResultSet, 1);\r
+ }\r
+ else\r
+ {\r
+ // generate code to evaluate CHECK CONSTRAINTS\r
+ generateCheckConstraints( checkConstraints, acb, mb ); // arg 2\r
+\r
+ if(isDependentTable)\r
+ {\r
+ mb.push(acb.addItem(makeConstantAction()));\r
+ mb.push(acb.addItem(makeResultDescription()));\r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, "getDeleteCascadeUpdateResultSet",\r
+ ClassName.ResultSet, 4);\r
+ }else\r
+ {\r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, "getUpdateResultSet",\r
+ ClassName.ResultSet, 2);\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Return the type of statement, something from\r
+ * StatementType.\r
+ *\r
+ * @return the type of statement\r
+ */\r
+ protected final int getStatementType()\r
+ {\r
+ return StatementType.UPDATE;\r
+ }\r
+\r
+\r
+ /**\r
+ * Gets the map of all columns which must be read out of the base table.\r
+ * These are the columns needed to<UL>:\r
+ * <LI>maintain indices</LI>\r
+ * <LI>maintain foreign keys</LI>\r
+ * <LI>support Replication's Delta Optimization</LI></UL>\r
+ * <p>\r
+ * The returned map is a FormatableBitSet with 1 bit for each column in the\r
+ * table plus an extra, unsued 0-bit. If a 1-based column id must\r
+ * be read from the base table, then the corresponding 1-based bit\r
+ * is turned ON in the returned FormatableBitSet.\r
+ * <p> \r
+ * <B>NOTE</B>: this method is not expected to be called when\r
+ * all columns are being updated (i.e. updateColumnList is null).\r
+ *\r
+ * @param dd the data dictionary to look in\r
+ * @param baseTable the base table descriptor\r
+ * @param updateColumnList the rcl for the update. CANNOT BE NULL\r
+ *\r
+ * @return a FormatableBitSet of columns to be read out of the base table\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public FormatableBitSet getReadMap\r
+ (\r
+ DataDictionary dd,\r
+ TableDescriptor baseTable,\r
+ ResultColumnList updateColumnList\r
+ )\r
+ throws StandardException\r
+ {\r
+ boolean[] needsDeferredProcessing = new boolean[1];\r
+ needsDeferredProcessing[0] = requiresDeferredProcessing();\r
+\r
+ Vector conglomVector = new Vector();\r
+ relevantCdl = new ConstraintDescriptorList();\r
+ relevantTriggers = new GenericDescriptorList();\r
+\r
+ FormatableBitSet columnMap = UpdateNode.getUpdateReadMap(baseTable,\r
+ updateColumnList, conglomVector, relevantCdl, relevantTriggers, needsDeferredProcessing );\r
+\r
+ markAffectedIndexes( conglomVector );\r
+\r
+ adjustDeferredFlag( needsDeferredProcessing[0] );\r
+\r
+ return columnMap;\r
+ }\r
+\r
+\r
+ /**\r
+ * Construct the changedColumnIds array. Note we sort its entries by\r
+ * columnId.\r
+ */\r
+ private int[] getChangedColumnIds(ResultColumnList rcl)\r
+ {\r
+ if (rcl == null) { return (int[])null; }\r
+ else { return rcl.sortMe(); }\r
+ }\r
+ /**\r
+ * Builds a bitmap of all columns which should be read from the\r
+ * Store in order to satisfy an UPDATE statement.\r
+ *\r
+ * Is passed a list of updated columns. Does the following:\r
+ *\r
+ * 1) finds all indices which overlap the updated columns\r
+ * 2) adds the index columns to a bitmap of affected columns\r
+ * 3) adds the index descriptors to a list of conglomerate\r
+ * descriptors.\r
+ * 4) finds all constraints which overlap the updated columns\r
+ * and adds the constrained columns to the bitmap\r
+ * 5) finds all triggers which overlap the updated columns.\r
+ * 6) if there are any triggers, marks all columns in the bitmap\r
+ * 7) adds the triggers to an evolving list of triggers\r
+ *\r
+ * @param updateColumnList a list of updated columns\r
+ * @param conglomVector OUT: vector of affected indices\r
+ * @param relevantConstraints IN/OUT. Empty list is passed in. We hang constraints on it as we go.\r
+ * @param relevantTriggers IN/OUT. Passed in as an empty list. Filled in as we go.\r
+ * @param needsDeferredProcessing IN/OUT. true if the statement already needs\r
+ * deferred processing. set while evaluating this\r
+ * routine if a trigger or constraint requires\r
+ * deferred processing\r
+ *\r
+ * @return a FormatableBitSet of columns to be read out of the base table\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public static FormatableBitSet getUpdateReadMap\r
+ (\r
+ TableDescriptor baseTable,\r
+ ResultColumnList updateColumnList,\r
+ Vector conglomVector,\r
+ ConstraintDescriptorList relevantConstraints,\r
+ GenericDescriptorList relevantTriggers,\r
+ boolean[] needsDeferredProcessing\r
+ )\r
+ throws StandardException\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(updateColumnList != null, "updateColumnList is null");\r
+ }\r
+\r
+ int columnCount = baseTable.getMaxColumnID();\r
+ FormatableBitSet columnMap = new FormatableBitSet(columnCount + 1);\r
+\r
+ /*\r
+ ** Add all the changed columns. We don't strictly\r
+ ** need the before image of the changed column in all cases,\r
+ ** but it makes life much easier since things are set\r
+ ** up around the assumption that we have the before\r
+ ** and after image of the column.\r
+ */\r
+ int[] changedColumnIds = updateColumnList.sortMe();\r
+\r
+ for (int ix = 0; ix < changedColumnIds.length; ix++)\r
+ {\r
+ columnMap.set(changedColumnIds[ix]);\r
+ }\r
+\r
+ /* \r
+ ** Get a list of the indexes that need to be \r
+ ** updated. ColumnMap contains all indexed\r
+ ** columns where 1 or more columns in the index\r
+ ** are going to be modified.\r
+ */\r
+ DMLModStatementNode.getXAffectedIndexes(baseTable, updateColumnList, columnMap, conglomVector );\r
+ \r
+ /* \r
+ ** Add all columns needed for constraints. We don't\r
+ ** need to bother with foreign key/primary key constraints\r
+ ** because they are added as a side effect of adding\r
+ ** their indexes above.\r
+ */\r
+ baseTable.getAllRelevantConstraints\r
+ ( StatementType.UPDATE, false, changedColumnIds, needsDeferredProcessing, relevantConstraints );\r
+\r
+ int rclSize = relevantConstraints.size();\r
+ for (int index = 0; index < rclSize; index++)\r
+ {\r
+ ConstraintDescriptor cd = relevantConstraints.elementAt(index);\r
+ if (cd.getConstraintType() != DataDictionary.CHECK_CONSTRAINT)\r
+ {\r
+ continue;\r
+ }\r
+\r
+ int[] refColumns = ((CheckConstraintDescriptor)cd).getReferencedColumns();\r
+ for (int i = 0; i < refColumns.length; i++)\r
+ {\r
+ columnMap.set(refColumns[i]);\r
+ }\r
+ }\r
+\r
+ /*\r
+ ** If we have any triggers, then get all the columns\r
+ ** because we don't know what the user will ultimately\r
+ ** reference.\r
+ */\r
+\r
+ baseTable.getAllRelevantTriggers( StatementType.UPDATE, changedColumnIds, relevantTriggers );\r
+ if ( relevantTriggers.size() > 0 ) { needsDeferredProcessing[0] = true; }\r
+\r
+ if (relevantTriggers.size() > 0)\r
+ {\r
+ for (int i = 1; i <= columnCount; i++)\r
+ {\r
+ columnMap.set(i);\r
+ }\r
+ }\r
+\r
+ return columnMap;\r
+ }\r
+\r
+ /*\r
+ * Force correlated column references in the SET clause to have the\r
+ * name of the base table. This dances around the problem alluded to\r
+ * in scrubResultColumn().\r
+ */\r
+ private void normalizeCorrelatedColumns( ResultColumnList rcl, FromTable fromTable )\r
+ throws StandardException\r
+ {\r
+ String correlationName = fromTable.getCorrelationName();\r
+\r
+ if ( correlationName == null ) { return; }\r
+\r
+ TableName tableNameNode;\r
+\r
+ if ( fromTable instanceof CurrentOfNode )\r
+ { tableNameNode = ((CurrentOfNode) fromTable).getBaseCursorTargetTableName(); }\r
+ else { tableNameNode = makeTableName( null, fromTable.getBaseTableName() ); }\r
+ \r
+ int count = rcl.size();\r
+\r
+ for ( int i = 0; i < count; i++ )\r
+ {\r
+ ResultColumn column = (ResultColumn) rcl.elementAt( i );\r
+ ColumnReference reference = column.getReference();\r
+\r
+ if ( (reference != null) && correlationName.equals( reference.getTableName() ) )\r
+ {\r
+ reference.setTableNameNode( tableNameNode );\r
+ }\r
+ }\r
+ \r
+ }\r
+\r
+ /**\r
+ * Check table name and then clear it from the result set columns.\r
+ * \r
+ * @exception StandardExcepion if invalid column/table is specified.\r
+ */\r
+ private void checkTableNameAndScrubResultColumns(ResultColumnList rcl) \r
+ throws StandardException\r
+ {\r
+ int columnCount = rcl.size();\r
+ int tableCount = ((SelectNode)resultSet).fromList.size();\r
+\r
+ for ( int i = 0; i < columnCount; i++ )\r
+ {\r
+ boolean foundMatchingTable = false; \r
+ ResultColumn column = (ResultColumn) rcl.elementAt( i );\r
+\r
+ if (column.getTableName() != null) {\r
+ for (int j = 0; j < tableCount; j++) {\r
+ FromTable fromTable = (FromTable) ((SelectNode)resultSet).\r
+ fromList.elementAt(j);\r
+ final String tableName;\r
+ if ( fromTable instanceof CurrentOfNode ) { \r
+ tableName = ((CurrentOfNode)fromTable).\r
+ getBaseCursorTargetTableName().getTableName();\r
+ } else { \r
+ tableName = fromTable.getBaseTableName();\r
+ }\r
+\r
+ if (column.getTableName().equals(tableName)) {\r
+ foundMatchingTable = true;\r
+ break;\r
+ }\r
+ }\r
+\r
+ if (!foundMatchingTable) {\r
+ throw StandardException.newException(\r
+ SQLState.LANG_COLUMN_NOT_FOUND, \r
+ column.getTableName() + "." + column.getName());\r
+ }\r
+ }\r
+\r
+ /* The table name is\r
+ * unnecessary for an update. More importantly, though, it\r
+ * creates a problem in the degenerate case with a positioned\r
+ * update. The user must specify the base table name for a\r
+ * positioned update. If a correlation name was specified for\r
+ * the cursor, then a match for the ColumnReference would not\r
+ * be found if we didn't null out the name. (Aren't you\r
+ * glad you asked?)\r
+ */\r
+ column.clearTableName();\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Normalize synonym column references to have the name of the base table. \r
+ *\r
+ * @param rcl The result column list of the target table\r
+ * @param fromTable The table name to set the column refs to\r
+ * \r
+ * @exception StandardException Thrown on error\r
+ */\r
+ private void normalizeSynonymColumns(\r
+ ResultColumnList rcl, \r
+ FromTable fromTable)\r
+ throws StandardException\r
+ {\r
+ if (fromTable.getCorrelationName() != null) \r
+ { \r
+ return; \r
+ }\r
+ \r
+ TableName tableNameNode;\r
+ if (fromTable instanceof CurrentOfNode)\r
+ { \r
+ tableNameNode = \r
+ ((CurrentOfNode) fromTable).getBaseCursorTargetTableName(); \r
+ }\r
+ else \r
+ { \r
+ tableNameNode = makeTableName(null, fromTable.getBaseTableName()); \r
+ }\r
+ \r
+ super.normalizeSynonymColumns(rcl, tableNameNode);\r
+ }\r
+ \r
+} // end of UpdateNode\r