--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.execute.UpdateResultSet\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.execute;\r
+\r
+import java.util.Hashtable;\r
+import java.util.Properties;\r
+\r
+import org.apache.derby.iapi.db.TriggerExecutionContext;\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.services.io.FormatableBitSet;\r
+import org.apache.derby.iapi.services.io.StreamStorable;\r
+import org.apache.derby.iapi.services.loader.GeneratedMethod;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import org.apache.derby.iapi.sql.Activation;\r
+import org.apache.derby.iapi.sql.ResultDescription;\r
+import org.apache.derby.iapi.sql.ResultSet;\r
+import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;\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.ExecRow;\r
+import org.apache.derby.iapi.sql.execute.NoPutResultSet;\r
+import org.apache.derby.iapi.sql.execute.RowChanger;\r
+import org.apache.derby.iapi.store.access.ConglomerateController;\r
+import org.apache.derby.iapi.store.access.ScanController;\r
+import org.apache.derby.iapi.store.access.TransactionController;\r
+import org.apache.derby.iapi.types.BooleanDataValue;\r
+import org.apache.derby.iapi.types.DataValueDescriptor;\r
+import org.apache.derby.iapi.types.RowLocation;\r
+\r
+/**\r
+ * Update the rows from the specified\r
+ * base table. This will cause constraints to be checked\r
+ * and triggers to be executed based on the c's and t's\r
+ * compiled into the update plan.\r
+ *\r
+ */\r
+class UpdateResultSet extends DMLWriteResultSet\r
+{\r
+ private TransactionController tc;\r
+ private ExecRow newBaseRow;\r
+ private ExecRow row;\r
+ private ExecRow deferredSparseRow;\r
+ UpdateConstantAction constants;\r
+ \r
+ private ResultDescription resultDescription;\r
+ private NoPutResultSet source;\r
+ NoPutResultSet savedSource;\r
+ private RowChanger rowChanger;\r
+\r
+ protected ConglomerateController deferredBaseCC;\r
+\r
+ protected long[] deferredUniqueCIDs;\r
+ protected boolean[] deferredUniqueCreated;\r
+ protected ConglomerateController deferredUniqueCC[];\r
+ protected ScanController[] deferredUniqueScans;\r
+\r
+ private TemporaryRowHolderImpl deletedRowHolder;\r
+ private TemporaryRowHolderImpl insertedRowHolder;\r
+\r
+ // cached \r
+ private RISetChecker riChecker;\r
+ private TriggerInfo triggerInfo;\r
+ private TriggerEventActivator triggerActivator;\r
+ private boolean updatingReferencedKey;\r
+ private boolean updatingForeignKey;\r
+ private int numOpens;\r
+ private long heapConglom; \r
+ private FKInfo[] fkInfoArray;\r
+ private FormatableBitSet baseRowReadList;\r
+ private GeneratedMethod checkGM;\r
+ private int resultWidth;\r
+ private int numberOfBaseColumns;\r
+ private ExecRow deferredTempRow;\r
+ private ExecRow deferredBaseRow;\r
+ private ExecRow oldDeletedRow;\r
+ private ResultDescription triggerResultDescription;\r
+\r
+ int lockMode;\r
+ boolean deferred;\r
+ boolean beforeUpdateCopyRequired = false;\r
+\r
+ /**\r
+ * Returns the description of the updated rows.\r
+ * REVISIT: Do we want this to return NULL instead?\r
+ */\r
+ public ResultDescription getResultDescription()\r
+ {\r
+ return resultDescription;\r
+ }\r
+\r
+ /*\r
+ * class interface\r
+ *\r
+ */\r
+ /**\r
+ * @param source update rows come from source\r
+ * @param checkGM Generated method for enforcing check constraints\r
+ * @exception StandardException thrown on error\r
+ */\r
+ UpdateResultSet(NoPutResultSet source,\r
+ GeneratedMethod checkGM,\r
+ Activation activation)\r
+ throws StandardException\r
+ {\r
+ this(source, checkGM , activation, activation.getConstantAction(),null);\r
+ }\r
+\r
+ /*\r
+ * class interface\r
+ *\r
+ */\r
+ /**\r
+ * @param source update rows come from source\r
+ * @param checkGM Generated method for enforcing check constraints\r
+ * @param activation Activation\r
+ * @param constantActionItem id of the update constant action saved objec\r
+ * @param rsdItem id of the Result Description saved object\r
+ * @exception StandardException thrown on error\r
+ */\r
+ UpdateResultSet(NoPutResultSet source,\r
+ GeneratedMethod checkGM,\r
+ Activation activation, \r
+ int constantActionItem,\r
+ int rsdItem)\r
+ throws StandardException\r
+ {\r
+ this(source, checkGM , activation,\r
+ ((ConstantAction)activation.getPreparedStatement().getSavedObject(constantActionItem)),\r
+ (ResultDescription) activation.getPreparedStatement().getSavedObject(rsdItem));\r
+ \r
+ // In case of referential action update, we do a deferred updates\r
+ deferred = true;\r
+ }\r
+\r
+\r
+ /*\r
+ * class interface\r
+ *\r
+ */\r
+ /**\r
+ * @param source update rows come from source\r
+ * @param checkGM Generated method for enforcing check constraints\r
+ * @exception StandardException thrown on error\r
+ */\r
+ UpdateResultSet(NoPutResultSet source,\r
+ GeneratedMethod checkGM,\r
+ Activation activation,\r
+ ConstantAction passedInConstantAction,\r
+ ResultDescription passedInRsd)\r
+ throws StandardException\r
+ {\r
+ super(activation, passedInConstantAction);\r
+\r
+ // Get the current transaction controller\r
+ tc = activation.getTransactionController();\r
+ this.source = source;\r
+ this.checkGM = checkGM;\r
+\r
+ constants = (UpdateConstantAction) constantAction;\r
+ fkInfoArray = constants.getFKInfo();\r
+ triggerInfo = constants.getTriggerInfo();\r
+\r
+ heapConglom = constants.conglomId;\r
+\r
+ baseRowReadList = constants.getBaseRowReadList();\r
+ if(passedInRsd ==null)\r
+ resultDescription = source.getResultDescription();\r
+ else\r
+ resultDescription = passedInRsd;\r
+ /*\r
+ ** We NEED a result description when we are going to\r
+ ** to have to kick off a trigger. In a replicated environment\r
+ ** we don't get a result description when we are replaying\r
+ ** source xacts on the target, which should never be the\r
+ ** case for an UpdateResultSet.\r
+ */\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (resultDescription == null)\r
+ {\r
+ SanityManager.ASSERT(triggerInfo == null, "triggers need a result description to pass to result sets given to users");\r
+ }\r
+ }\r
+\r
+ if (fkInfoArray != null)\r
+ {\r
+ for (int i = 0; i < fkInfoArray.length; i++)\r
+ {\r
+ if (fkInfoArray[i].type == FKInfo.REFERENCED_KEY)\r
+ {\r
+ updatingReferencedKey = true;\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(constants.deferred, "updating referenced key but update not deferred, wuzzup?");\r
+ }\r
+ }\r
+ else\r
+ { \r
+ updatingForeignKey = true;\r
+ }\r
+ }\r
+ }\r
+\r
+ /* Get the # of columns in the ResultSet */\r
+ resultWidth = resultDescription.getColumnCount();\r
+ \r
+ /*\r
+ ** Calculate the # of columns in the base table. The result set\r
+ ** contains the before columns, the after columns, and the RowLocation,\r
+ ** so the number of base columns is half of the number of result set\r
+ ** columns, after subtracting one for the row location column.\r
+ */\r
+ numberOfBaseColumns = (resultWidth - 1) / 2;\r
+ \r
+ /* Get the new base row */\r
+ newBaseRow = RowUtil.getEmptyValueRow(numberOfBaseColumns, lcc);\r
+\r
+ deferred = constants.deferred;\r
+ \r
+ //update can be marked for deferred mode because the scan is being done\r
+ //using index. But it is not necesary to keep the before copy\r
+ //of the row in the temporary row holder (deletedRowHolder) unless\r
+ //there are RI constraint or Triggers.(beetle:5301)\r
+ if(triggerInfo != null || fkInfoArray !=null){\r
+ beforeUpdateCopyRequired = true;\r
+ }\r
+ \r
+ }\r
+ /**\r
+ @exception StandardException Standard Derby error policy\r
+ */\r
+ public void open() throws StandardException\r
+ {\r
+\r
+ setup();\r
+ collectAffectedRows();\r
+\r
+ /*\r
+ ** If this is a deferred update, read the new rows and RowLocations\r
+ ** from the temporary conglomerate and update the base table using\r
+ ** the RowChanger.\r
+ */\r
+ if (deferred)\r
+ {\r
+\r
+ runChecker(true); //check for only RESTRICT referential action rule violations\r
+ fireBeforeTriggers();\r
+ updateDeferredRows();\r
+ /* Apply deferred inserts to unique indexes */\r
+ rowChanger.finish();\r
+ runChecker(false); //check for all violations\r
+ fireAfterTriggers();\r
+\r
+ }\r
+ else{\r
+ /* Apply deferred inserts to unique indexes */\r
+ rowChanger.finish();\r
+ }\r
+\r
+ cleanUp();\r
+ }\r
+\r
+\r
+ /**\r
+ @exception StandardException Standard Derby error policy\r
+ */\r
+ void setup() throws StandardException\r
+ {\r
+ super.setup();\r
+\r
+ /* decode lock mode */\r
+ lockMode = decodeLockMode(constants.lockMode);\r
+\r
+ boolean firstOpen = (rowChanger == null);\r
+\r
+ rowCount = 0;\r
+ \r
+ /* Cache query plan text for source, before it gets blown away */\r
+ if (lcc.getRunTimeStatisticsMode())\r
+ {\r
+ /* savedSource nulled after run time statistics generation */\r
+ savedSource = source;\r
+ }\r
+\r
+ /* Get or re-use the row changer.\r
+ * NOTE: We need to set ourself as the top result set\r
+ * if this is not the 1st execution. (Done in constructor\r
+ * for 1st execution.)\r
+ */\r
+ if (firstOpen)\r
+ {\r
+ rowChanger = lcc.getLanguageConnectionFactory().getExecutionFactory()\r
+ .getRowChanger( heapConglom, \r
+ constants.heapSCOCI, \r
+ heapDCOCI,\r
+ constants.irgs,\r
+ constants.indexCIDS,\r
+ constants.indexSCOCIs,\r
+ indexDCOCIs,\r
+ constants.numColumns,\r
+ tc,\r
+ constants.changedColumnIds,\r
+ constants.getBaseRowReadList(),\r
+ constants.getBaseRowReadMap(),\r
+ constants.getStreamStorableHeapColIds(),\r
+ activation);\r
+ rowChanger.setIndexNames(constants.indexNames);\r
+ }\r
+ else\r
+ {\r
+ lcc.getStatementContext().setTopResultSet(this, subqueryTrackingArray);\r
+ }\r
+\r
+\r
+ /* Open the RowChanger before the source ResultSet so that\r
+ * the store will see the RowChanger's lock as a covering lock\r
+ * if it is a table lock.\r
+ */\r
+ rowChanger.open(lockMode);\r
+\r
+ if (numOpens++ == 0)\r
+ {\r
+ source.openCore();\r
+ }\r
+ else\r
+ {\r
+ source.reopenCore();\r
+ }\r
+\r
+ /* The source does not know whether or not we are doing a\r
+ * deferred mode update. If we are, then we must clear the\r
+ * index scan info from the activation so that the row changer\r
+ * does not re-use that information (which won't be valid for\r
+ * a deferred mode update).\r
+ */\r
+ if (deferred)\r
+ {\r
+ activation.clearIndexScanInfo();\r
+ }\r
+\r
+ if (fkInfoArray != null)\r
+ {\r
+ if (riChecker == null)\r
+ {\r
+ riChecker = new RISetChecker(tc, fkInfoArray);\r
+ }\r
+ else\r
+ {\r
+ riChecker.reopen();\r
+ }\r
+ }\r
+\r
+ if (deferred)\r
+ {\r
+ /* Allocate the temporary rows and get result description\r
+ * if this is the 1st time that we are executing.\r
+ */\r
+ if (firstOpen)\r
+ {\r
+ deferredTempRow = RowUtil.getEmptyValueRow(numberOfBaseColumns+1, lcc);\r
+ oldDeletedRow = RowUtil.getEmptyValueRow(numberOfBaseColumns, lcc);\r
+ triggerResultDescription = (resultDescription != null) ?\r
+ resultDescription.truncateColumns(numberOfBaseColumns+1) :\r
+ null;\r
+ }\r
+\r
+ Properties properties = new Properties();\r
+\r
+ // Get the properties on the heap\r
+ rowChanger.getHeapConglomerateController().getInternalTablePropertySet(properties);\r
+ if(beforeUpdateCopyRequired){\r
+ deletedRowHolder =\r
+ new TemporaryRowHolderImpl(activation, properties,\r
+ triggerResultDescription);\r
+ }\r
+ insertedRowHolder =\r
+ new TemporaryRowHolderImpl(activation, properties,\r
+ triggerResultDescription);\r
+\r
+ rowChanger.setRowHolder(insertedRowHolder);\r
+ }\r
+\r
+ } \r
+\r
+ /* Following 2 methods are for checking and make sure we don't have one un-objectified stream\r
+ * to be inserted into 2 temp table rows for deferred update. Otherwise it would cause problem\r
+ * when writing to disk using the stream a second time. In other cases we don't want to\r
+ * unnecessarily objectify the stream. beetle 4896.\r
+ */\r
+ private FormatableBitSet checkStreamCols()\r
+ {\r
+ DataValueDescriptor[] cols = row.getRowArray();\r
+ FormatableBitSet streamCols = null;\r
+ for (int i = 0; i < numberOfBaseColumns; i++)\r
+ {\r
+ if (cols[i+numberOfBaseColumns] instanceof StreamStorable) //check new values\r
+ {\r
+ if (streamCols == null) streamCols = new FormatableBitSet(numberOfBaseColumns);\r
+ streamCols.set(i);\r
+ }\r
+ }\r
+ return streamCols;\r
+ }\r
+\r
+ private void objectifyStream(ExecRow tempRow, FormatableBitSet streamCols) throws StandardException\r
+ {\r
+ DataValueDescriptor[] cols = tempRow.getRowArray();\r
+ for (int i = 0; i < numberOfBaseColumns; i++)\r
+ {\r
+ if (cols[i] != null && streamCols.get(i))\r
+ ((StreamStorable)cols[i]).loadStream();\r
+ }\r
+ }\r
+\r
+ public boolean collectAffectedRows() throws StandardException\r
+ {\r
+\r
+ boolean rowsFound = false;\r
+ row = getNextRowCore(source);\r
+ if (row!=null)\r
+ rowsFound = true;\r
+ else\r
+ {\r
+ activation.addWarning(\r
+ StandardException.newWarning(\r
+ SQLState.LANG_NO_ROW_FOUND));\r
+ }\r
+\r
+ //beetle 3865, update cursor use index.\r
+ TableScanResultSet tableScan = (TableScanResultSet) activation.getForUpdateIndexScan();\r
+ boolean notifyCursor = ((tableScan != null) && ! tableScan.sourceDrained);\r
+ boolean checkStream = (deferred && rowsFound && ! constants.singleRowSource);\r
+ FormatableBitSet streamCols = (checkStream ? checkStreamCols() : null);\r
+ checkStream = (streamCols != null);\r
+\r
+ while ( row != null )\r
+ {\r
+\r
+ /* By convention, the last column in the result set for an\r
+ * update contains a SQLRef containing the RowLocation of\r
+ * the row to be updated.\r
+ */\r
+\r
+ /*\r
+ ** If we're doing deferred update, write the new row and row\r
+ ** location to the temporary conglomerate. If we're not doing\r
+ ** deferred update, update the permanent conglomerates now\r
+ ** using the RowChanger.\r
+ */\r
+ if (deferred)\r
+ {\r
+ /*\r
+ ** If we have a before trigger, we must evaluate the \r
+ ** check constraint after we have executed the trigger.\r
+ ** Note that we have compiled checkGM accordingly (to\r
+ ** handle the different row shape if we are evaluating\r
+ ** against the input result set or a temporary row holder\r
+ ** result set).\r
+ */\r
+ if (triggerInfo == null)\r
+ {\r
+ evaluateCheckConstraints( checkGM, activation );\r
+ }\r
+\r
+ /*\r
+ ** We are going to only save off the updated\r
+ ** columns and the RID. For a trigger, all columns\r
+ ** were marked as needed so we'll copy them all.\r
+ */\r
+ RowUtil.copyRefColumns(deferredTempRow,\r
+ row,\r
+ numberOfBaseColumns,\r
+ numberOfBaseColumns + 1);\r
+ if (checkStream)\r
+ objectifyStream(deferredTempRow, streamCols);\r
+\r
+ insertedRowHolder.insert(deferredTempRow); \r
+\r
+ /*\r
+ ** Grab a copy of the row to delete. We are\r
+ ** going to use this for deferred RI checks.\r
+ */\r
+ if(beforeUpdateCopyRequired)\r
+ {\r
+ RowUtil.copyRefColumns(oldDeletedRow,\r
+ row,\r
+ numberOfBaseColumns);\r
+\r
+ deletedRowHolder.insert(oldDeletedRow);\r
+ }\r
+\r
+ /*\r
+ ** If we haven't already, lets get a template to\r
+ ** use as a template for our rescan of the base table.\r
+ ** Do this now while we have a real row to use\r
+ ** as a copy.\r
+ **\r
+ ** There is one less column in the base row than\r
+ ** there is in source row, because the base row\r
+ ** doesn't contain the row location.\r
+ */\r
+ if (deferredBaseRow == null)\r
+ {\r
+ deferredBaseRow = RowUtil.getEmptyValueRow(numberOfBaseColumns, lcc);\r
+ \r
+ RowUtil.copyCloneColumns(deferredBaseRow, row, \r
+ numberOfBaseColumns);\r
+\r
+ /*\r
+ ** While we're here, let's also create a sparse row for\r
+ ** fetching from the store.\r
+ */\r
+ deferredSparseRow = makeDeferredSparseRow(deferredBaseRow,\r
+ baseRowReadList,\r
+ lcc);\r
+ }\r
+ }\r
+ else\r
+ {\r
+ evaluateCheckConstraints( checkGM, activation );\r
+\r
+ /* Get the RowLocation to update \r
+ * NOTE - Column #s in the Row are 1 based.\r
+ */\r
+ RowLocation baseRowLocation = (RowLocation)\r
+ (row.getColumn(resultWidth)).getObject();\r
+\r
+ RowUtil.copyRefColumns(newBaseRow,\r
+ row,\r
+ numberOfBaseColumns,\r
+ numberOfBaseColumns);\r
+\r
+ if (riChecker != null)\r
+ {\r
+ /*\r
+ ** Make sure all foreign keys in the new row\r
+ ** are maintained. Note that we don't bother \r
+ ** checking primary/unique keys that are referenced\r
+ ** here. The reason is that if we are updating\r
+ ** a referenced key, we'll be updating in deferred\r
+ ** mode, so we wont get here.\r
+ */\r
+ riChecker.doFKCheck(newBaseRow);\r
+ }\r
+\r
+ source.updateRow(newBaseRow);\r
+ rowChanger.updateRow(row,newBaseRow,baseRowLocation);\r
+\r
+ //beetle 3865, update cursor use index.\r
+ if (notifyCursor)\r
+ notifyForUpdateCursor(row.getRowArray(),newBaseRow.getRowArray(),baseRowLocation,\r
+ tableScan);\r
+ }\r
+\r
+ rowCount++;\r
+\r
+ // No need to do a next on a single row source\r
+ if (constants.singleRowSource)\r
+ {\r
+ row = null;\r
+ }\r
+ else\r
+ {\r
+ row = getNextRowCore(source);\r
+ }\r
+ }\r
+\r
+ return rowsFound;\r
+ }\r
+\r
+ /* beetle 3865, updateable cursor use index. If the row we are updating has new value that\r
+ * falls into the direction of the index scan of the cursor, we save this rid into a hash table\r
+ * (for fast search), so that when the cursor hits it again, it knows to skip it. When we get\r
+ * to a point that the hash table is full, we scan forward the cursor until one of two things\r
+ * happen: (1) we hit a record whose rid is in the hash table (we went through it already, so\r
+ * skip it), we remove it from hash table, so that we can continue to use hash table. OR, (2) the scan\r
+ * forward hit the end. If (2) happens, we can de-reference the hash table to make it available\r
+ * for garbage collection. We save the future row id's in a virtual mem heap. In any case,\r
+ * next read will use a row id that we saved.\r
+ */\r
+ private void notifyForUpdateCursor(DataValueDescriptor[] row, DataValueDescriptor[] newBaseRow,\r
+ RowLocation rl, TableScanResultSet tableScan)\r
+ throws StandardException\r
+ {\r
+ int[] indexCols = tableScan.indexCols;\r
+ int[] changedCols = constants.changedColumnIds;\r
+ boolean placedForward = false, ascending, decided = false, overlap = false;\r
+ int basePos, k;\r
+ /* first of all, we see if there's overlap between changed column ids and index key\r
+ * columns. If so, we see if the new update value falls into the future range of the\r
+ * index scan, if so, we need to save it in hash table.\r
+ */\r
+ for (int i = 0; i < indexCols.length; i++)\r
+ {\r
+ basePos = indexCols[i];\r
+ if (basePos > 0)\r
+ ascending = true;\r
+ else\r
+ {\r
+ ascending = false;\r
+ basePos = -basePos;\r
+ }\r
+ for (int j = 0; j < changedCols.length; j++)\r
+ {\r
+ if (basePos == changedCols[j])\r
+ {\r
+ decided = true; //we pretty much decided if new row falls in front\r
+ //of the cursor or behind\r
+ /* the row and newBaseRow we get are compact base row that only have\r
+ * referenced columns. Our "basePos" is index in sparse heap row, so\r
+ * we need the BaseRowReadMap to map into the compact row.\r
+ */\r
+ int[] map = constants.getBaseRowReadMap();\r
+ if (map == null)\r
+ k = basePos - 1;\r
+ else\r
+ k = map[basePos - 1];\r
+\r
+ DataValueDescriptor key;\r
+ /* We need to compare with saved most-forward cursor scan key if we\r
+ * are reading records from the saved RowLocation temp table (instead\r
+ * of the old column value) because we only care if new update value\r
+ * jumps forward the most-forward scan key.\r
+ */\r
+ if (tableScan.compareToLastKey)\r
+ key = tableScan.lastCursorKey.getColumn(i + 1);\r
+ else\r
+ key = row[k];\r
+\r
+ /* Starting from the first index key column forward, we see if the direction\r
+ * of the update change is consistent with the direction of index scan.\r
+ * If so, we save it in hash table.\r
+ */\r
+ if ((ascending && key.greaterThan(newBaseRow[k], key).equals(true)) ||\r
+ (!ascending && key.lessThan(newBaseRow[k], key).equals(true)))\r
+ placedForward = true;\r
+ else if (key.equals(newBaseRow[k], key).equals(true))\r
+ {\r
+ decided = false;\r
+ overlap = true;\r
+ }\r
+ break;\r
+ }\r
+ }\r
+ if (decided) // already decided if new row falls in front or behind\r
+ break;\r
+ }\r
+ /* If index row gets updated but key value didn't actually change, we still\r
+ * put it in hash table because it can either fall in front or behind. This\r
+ * can happen if the update explicitly sets a value, but same as old.\r
+ */\r
+ if (overlap && !decided)\r
+ placedForward = true;\r
+\r
+ if (placedForward) // add it to hash table\r
+ {\r
+ /* determining initial capacity of hash table from a few factors:\r
+ * (1) user specified MAX_MEMORY_PER_TABLE property, (2) min value 100\r
+ * (3) optimizer estimated row count. We want to avoid re-hashing if\r
+ * possible, for performance reason, yet don't waste space. If initial\r
+ * capacity is greater than max size divided by load factor, no rehash\r
+ * is ever needed.\r
+ */\r
+ int maxCapacity = lcc.getOptimizerFactory().getMaxMemoryPerTable() / 16;\r
+ if (maxCapacity < 100)\r
+ maxCapacity = 100;\r
+\r
+ if (tableScan.past2FutureTbl == null)\r
+ {\r
+ double rowCount = tableScan.getEstimatedRowCount();\r
+ int initCapacity = 32 * 1024;\r
+ if (rowCount > 0.0)\r
+ {\r
+ rowCount = rowCount / 0.75 + 1.0; // load factor\r
+ if (rowCount < initCapacity)\r
+ initCapacity = (int) rowCount;\r
+ }\r
+ if (maxCapacity < initCapacity)\r
+ initCapacity = maxCapacity;\r
+\r
+ tableScan.past2FutureTbl = new Hashtable(initCapacity);\r
+ }\r
+\r
+ Hashtable past2FutureTbl = tableScan.past2FutureTbl;\r
+ /* If hash table is not full, we add it in. The key of the hash entry\r
+ * is the string value of the RowLocation. If the hash table is full,\r
+ * as the comments above this function say, we scan forward.\r
+ *\r
+ * Need to save a clone because when we get cached currentRow, "rl" shares the\r
+ * same reference, so is changed at the same time.\r
+ */\r
+ RowLocation updatedRL = (RowLocation) rl.getClone();\r
+\r
+ if (past2FutureTbl.size() < maxCapacity)\r
+ past2FutureTbl.put(updatedRL, updatedRL);\r
+ else\r
+ {\r
+ tableScan.skipFutureRowHolder = true;\r
+ ExecRow rlRow = new ValueRow(1);\r
+\r
+ for (;;)\r
+ {\r
+ ExecRow aRow = tableScan.getNextRowCore();\r
+ if (aRow == null)\r
+ {\r
+ tableScan.sourceDrained = true;\r
+ tableScan.past2FutureTbl = null; // de-reference for garbage coll.\r
+ break;\r
+ }\r
+ RowLocation rowLoc = (RowLocation) aRow.getColumn(aRow.nColumns());\r
+\r
+ if (updatedRL.equals(rowLoc)) //this row we are updating jumped forward\r
+ {\r
+ saveLastCusorKey(tableScan, aRow);\r
+ break; // don't need to worry about adding this row to hash any more\r
+ }\r
+\r
+ if (tableScan.futureForUpdateRows == null)\r
+ {\r
+ // virtual memory heap. In-memory part size 100. With the co-operation\r
+ // of hash table and in-memory part of heap (hash table shrinks while\r
+ // in-memory heap grows), hopefully we never spill temp table to disk.\r
+\r
+ tableScan.futureForUpdateRows = new TemporaryRowHolderImpl\r
+ (activation, null, null, 100, false, true);\r
+ }\r
+\r
+ rlRow.setColumn(1, rowLoc);\r
+ tableScan.futureForUpdateRows.insert(rlRow);\r
+ if (past2FutureTbl.size() < maxCapacity) //we got space in the hash table now, stop!\r
+ {\r
+ past2FutureTbl.put(updatedRL, updatedRL);\r
+ saveLastCusorKey(tableScan, aRow);\r
+ break;\r
+ }\r
+ }\r
+ tableScan.skipFutureRowHolder = false;\r
+ }\r
+ }\r
+ }\r
+\r
+ private void saveLastCusorKey(TableScanResultSet tableScan, ExecRow aRow) throws StandardException\r
+ {\r
+ /* We save the most-forward cursor scan key where we are stopping, so\r
+ * that next time when we decide if we need to put an updated row id into\r
+ * hash table, we can compare with this key. This is an optimization on\r
+ * memory usage of the hash table, otherwise it may be "leaking".\r
+ */\r
+ if (tableScan.lastCursorKey == null)\r
+ tableScan.lastCursorKey = new ValueRow(aRow.nColumns() - 1);\r
+ for (int i = 1; i <= tableScan.lastCursorKey.nColumns(); i++)\r
+ {\r
+ DataValueDescriptor aCol = aRow.getColumn(i);\r
+ if (aCol != null)\r
+ tableScan.lastCursorKey.setColumn(i, aCol.getClone());\r
+ }\r
+ }\r
+\r
+ void fireBeforeTriggers() throws StandardException\r
+ {\r
+ if (deferred)\r
+ {\r
+ if (triggerInfo != null)\r
+ {\r
+ if (triggerActivator == null)\r
+ {\r
+ triggerActivator = new TriggerEventActivator(lcc, \r
+ tc, \r
+ constants.targetUUID,\r
+ triggerInfo,\r
+ TriggerExecutionContext.UPDATE_EVENT,\r
+ activation, null);\r
+ }\r
+ else\r
+ {\r
+ triggerActivator.reopen();\r
+ }\r
+\r
+ // fire BEFORE trigger, do this before checking constraints\r
+ triggerActivator.notifyEvent(TriggerEvents.BEFORE_UPDATE, \r
+ deletedRowHolder.getResultSet(),\r
+ insertedRowHolder.getResultSet());\r
+\r
+ }\r
+ }\r
+ }\r
+\r
+ void fireAfterTriggers() throws StandardException\r
+ {\r
+ if (deferred)\r
+ {\r
+ if (triggerActivator != null)\r
+ {\r
+ triggerActivator.notifyEvent(TriggerEvents.AFTER_UPDATE, \r
+ deletedRowHolder.getResultSet(),\r
+ insertedRowHolder.getResultSet());\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+\r
+ void updateDeferredRows() throws StandardException\r
+ {\r
+ if (deferred)\r
+ {\r
+ // we already have everything locked \r
+ deferredBaseCC = \r
+ tc.openCompiledConglomerate(\r
+ false,\r
+ tc.OPENMODE_FORUPDATE|tc.OPENMODE_SECONDARY_LOCKED,\r
+ lockMode,\r
+ TransactionController.ISOLATION_SERIALIZABLE,\r
+ constants.heapSCOCI,\r
+ heapDCOCI);\r
+ \r
+ CursorResultSet rs = insertedRowHolder.getResultSet();\r
+ try\r
+ {\r
+ /*\r
+ ** We need to do a fetch doing a partial row\r
+ ** read. We need to shift our 1-based bit\r
+ ** set to a zero based bit set like the store\r
+ ** expects.\r
+ */\r
+ FormatableBitSet readBitSet = RowUtil.shift(baseRowReadList, 1);\r
+ ExecRow deferredTempRow2;\r
+\r
+ rs.open();\r
+ while ((deferredTempRow2 = rs.getNextRow()) != null)\r
+ {\r
+ /*\r
+ ** Check the constraint now if we have triggers.\r
+ ** Otherwise we evaluated them as we read the\r
+ ** rows in from the source.\r
+ */\r
+ if (triggerInfo != null)\r
+ {\r
+ source.setCurrentRow(deferredTempRow);\r
+ evaluateCheckConstraints(checkGM, activation);\r
+ }\r
+\r
+ /* \r
+ ** The last column is a Ref, which contains a \r
+ ** RowLocation.\r
+ */\r
+ DataValueDescriptor rlColumn = deferredTempRow2.getColumn(numberOfBaseColumns + 1);\r
+ RowLocation baseRowLocation = \r
+ (RowLocation) (rlColumn).getObject();\r
+ \r
+ /* Get the base row at the given RowLocation */\r
+ boolean row_exists = \r
+ deferredBaseCC.fetch(\r
+ baseRowLocation, deferredSparseRow.getRowArray(), \r
+ readBitSet);\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(row_exists, "did not find base row in deferred update");\r
+ }\r
+ \r
+ /*\r
+ ** Copy the columns from the temp row to the base row.\r
+ ** The base row has fewer columns than the temp row,\r
+ ** because it doesn't contain the row location.\r
+ */\r
+ RowUtil.copyRefColumns(newBaseRow,\r
+ deferredTempRow2,\r
+ numberOfBaseColumns);\r
+\r
+ rowChanger.updateRow(deferredBaseRow,\r
+ newBaseRow,\r
+ baseRowLocation);\r
+ source.updateRow(newBaseRow);\r
+ }\r
+ } finally\r
+ {\r
+ source.clearCurrentRow();\r
+ rs.close();\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ \r
+ void runChecker(boolean restrictCheckOnly) throws StandardException\r
+ {\r
+\r
+ /*\r
+ ** For a deferred update, make sure that there\r
+ ** aren't any primary keys that were removed which\r
+ ** are referenced. \r
+ */\r
+ if (deferred && updatingReferencedKey)\r
+ {\r
+ ExecRow deletedRow;\r
+ CursorResultSet deletedRows; \r
+\r
+ /*\r
+ ** For each referenced key that was modified\r
+ */\r
+ for (int i = 0; i < fkInfoArray.length; i++)\r
+ {\r
+ if (fkInfoArray[i].type == FKInfo.FOREIGN_KEY)\r
+ {\r
+ continue;\r
+ }\r
+\r
+ deletedRows = deletedRowHolder.getResultSet();\r
+ try\r
+ {\r
+ /*\r
+ ** For each delete row\r
+ */ \r
+ deletedRows.open();\r
+ while ((deletedRow = deletedRows.getNextRow()) != null)\r
+ {\r
+ if (!foundRow(deletedRow, \r
+ fkInfoArray[i].colArray, \r
+ insertedRowHolder))\r
+ {\r
+ riChecker.doRICheck(i, deletedRow, restrictCheckOnly);\r
+ }\r
+ } \r
+ }\r
+ finally\r
+ {\r
+ deletedRows.close();\r
+ }\r
+ }\r
+ }\r
+\r
+ /*\r
+ ** For a deferred update, make sure that there\r
+ ** aren't any foreign keys that were added that\r
+ ** aren't referenced. \r
+ */\r
+ if (deferred && updatingForeignKey)\r
+ {\r
+ ExecRow insertedRow;\r
+ CursorResultSet insertedRows; \r
+\r
+ /*\r
+ ** For each foreign key that was modified\r
+ */\r
+ for (int i = 0; i < fkInfoArray.length; i++)\r
+ {\r
+ if (fkInfoArray[i].type == FKInfo.REFERENCED_KEY)\r
+ {\r
+ continue;\r
+ }\r
+\r
+ insertedRows = insertedRowHolder.getResultSet();\r
+ try\r
+ {\r
+ /*\r
+ ** For each inserted row\r
+ */ \r
+ insertedRows.open();\r
+ while ((insertedRow = insertedRows.getNextRow()) != null)\r
+ {\r
+ if (!foundRow(insertedRow, \r
+ fkInfoArray[i].colArray, \r
+ deletedRowHolder))\r
+ {\r
+ riChecker.doRICheck(i, insertedRow, restrictCheckOnly);\r
+ }\r
+ } \r
+ }\r
+ finally\r
+ {\r
+ insertedRows.close();\r
+ }\r
+ }\r
+ }\r
+\r
+ }\r
+\r
+ public static boolean foundRow\r
+ (\r
+ ExecRow checkRow, \r
+ int[] colsToCheck,\r
+ TemporaryRowHolderImpl rowHolder\r
+ )\r
+ throws StandardException\r
+ {\r
+ ExecRow scanRow;\r
+ boolean foundMatch = false;\r
+ Object[] checkRowArray = checkRow.getRowArray();\r
+ DataValueDescriptor checkCol;\r
+ DataValueDescriptor scanCol;\r
+\r
+ CursorResultSet rs = rowHolder.getResultSet();\r
+ try\r
+ { \r
+ /*\r
+ ** For each inserted row\r
+ */ \r
+ rs.open();\r
+ while ((scanRow = rs.getNextRow()) != null)\r
+ {\r
+ Object[] scanRowArray = scanRow.getRowArray();\r
+ int i;\r
+ for (i = 0; i < colsToCheck.length; i++)\r
+ {\r
+ checkCol = (DataValueDescriptor)checkRowArray[colsToCheck[i]-1];\r
+ scanCol = (DataValueDescriptor)scanRowArray[colsToCheck[i]-1];\r
+\r
+ BooleanDataValue result = checkCol.equals(\r
+ scanCol,\r
+ checkCol); // result\r
+ if (!result.getBoolean())\r
+ {\r
+ break;\r
+ }\r
+ }\r
+ if (i == colsToCheck.length)\r
+ {\r
+ foundMatch = true;\r
+ break;\r
+ } \r
+ }\r
+ }\r
+ finally\r
+ {\r
+ rs.close();\r
+ }\r
+ return foundMatch;\r
+ }\r
+\r
+\r
+ /**\r
+ * @see ResultSet#cleanUp\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void cleanUp() throws StandardException\r
+ { \r
+ numOpens = 0;\r
+\r
+ /* Close down the source ResultSet tree */\r
+ if (source != null)\r
+ {\r
+ source.close();\r
+ // cache source across open()s\r
+ }\r
+\r
+ if (triggerActivator != null)\r
+ {\r
+ triggerActivator.cleanup();\r
+ // cache triggerActivator across open()s\r
+ }\r
+\r
+ if (rowChanger != null)\r
+ rowChanger.close();\r
+\r
+ if (deferredBaseCC != null)\r
+ deferredBaseCC.close();\r
+ deferredBaseCC = null;\r
+\r
+ if (insertedRowHolder != null)\r
+ {\r
+ insertedRowHolder.close();\r
+ }\r
+ \r
+ if (deletedRowHolder != null)\r
+ {\r
+ deletedRowHolder.close();\r
+ }\r
+\r
+ if (riChecker != null)\r
+ {\r
+ riChecker.close();\r
+ // cache riChecker across open()s\r
+ }\r
+\r
+ super.close();\r
+\r
+ endTime = getCurrentTimeMillis();\r
+ }\r
+\r
+ void rowChangerFinish() throws StandardException\r
+ {\r
+ rowChanger.finish();\r
+ }\r
+\r
+}\r