--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.execute.RowChangerImpl\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 org.apache.derby.iapi.services.io.FormatableBitSet;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;\r
+import org.apache.derby.iapi.sql.conn.StatementContext;\r
+import org.apache.derby.iapi.sql.dictionary.IndexRowGenerator;\r
+import org.apache.derby.iapi.sql.execute.ExecRow;\r
+import org.apache.derby.iapi.sql.execute.ExecutionContext;\r
+import org.apache.derby.iapi.sql.execute.RowChanger;\r
+import org.apache.derby.iapi.sql.execute.ExecutionFactory;\r
+import org.apache.derby.iapi.sql.execute.TemporaryRowHolder;\r
+\r
+import org.apache.derby.iapi.sql.Activation;\r
+\r
+import org.apache.derby.iapi.store.access.ConglomerateController;\r
+import org.apache.derby.iapi.store.access.DynamicCompiledOpenConglomInfo;\r
+import org.apache.derby.iapi.store.access.StaticCompiledOpenConglomInfo;\r
+import org.apache.derby.iapi.store.access.TransactionController;\r
+\r
+import org.apache.derby.iapi.types.DataValueDescriptor;\r
+\r
+import org.apache.derby.iapi.types.RowLocation;\r
+\r
+import java.util.Vector;\r
+\r
+/**\r
+ Perform row at a time DML operations of tables and maintain indexes.\r
+ */\r
+class RowChangerImpl implements RowChanger\r
+{\r
+ boolean isOpen = false;\r
+\r
+ //\r
+ //Stuff provided to the constructor\r
+ boolean[] fixOnUpdate = null;\r
+ long heapConglom;\r
+ DynamicCompiledOpenConglomInfo heapDCOCI;\r
+ StaticCompiledOpenConglomInfo heapSCOCI;\r
+ long[] indexCIDS = null;\r
+ DynamicCompiledOpenConglomInfo[] indexDCOCIs;\r
+ StaticCompiledOpenConglomInfo[] indexSCOCIs;\r
+ IndexRowGenerator[] irgs = null;\r
+ Activation activation;\r
+ TransactionController tc;\r
+ FormatableBitSet changedColumnBitSet; \r
+ FormatableBitSet baseRowReadList; \r
+ private int[] baseRowReadMap; //index=heap column, value=input row column.\r
+ int[] changedColumnIds;\r
+ TemporaryRowHolderImpl rowHolder;\r
+ \r
+ // for error reporting.\r
+ String[] indexNames;\r
+\r
+ //\r
+ //Stuff filled in by open\r
+ private ConglomerateController baseCC;\r
+ private RowLocation baseRowLocation;\r
+ private IndexSetChanger isc;\r
+\r
+ // a row array with all non-updated columns compacted out\r
+ private DataValueDescriptor[] sparseRowArray;\r
+ private int[] partialChangedColumnIds;\r
+ \r
+ /**\r
+ Create a new RowChanger for performing update and delete operations\r
+ based on partial before and after rows.\r
+\r
+ @param heapConglom Conglomerate # for the heap\r
+ @param heapSCOCI SCOCI for heap.\r
+ @param heapDCOCI DCOCI for heap\r
+ @param irgs the IndexRowGenerators for the table's indexes. We use\r
+ positions in this array as local id's for indexes. To support updates,\r
+ only indexes that change need be included.\r
+ @param indexCIDS the conglomerateids for the table's idexes. \r
+ indexCIDS[ix] corresponds to the same index as irgs[ix].\r
+ @param indexSCOCIs the SCOCIs for the table's idexes. \r
+ indexSCOCIs[ix] corresponds to the same index as irgs[ix].\r
+ @param indexDCOCIs the DCOCIs for the table's idexes. \r
+ indexDCOCIs[ix] corresponds to the same index as irgs[ix].\r
+ @param numberOfColumns Number of columns in partial write row.\r
+ @param changedColumnIdsInput array of 1 based ints indicating the columns\r
+ to be updated. Only used for updates\r
+ @param tc the transaction controller\r
+ @param baseRowReadList bit set of columns read from base row. 1 based.\r
+ @param baseRowReadMap BaseRowReadMap[heapColId]->ReadRowColumnId. (0 based)\r
+ @exception StandardException Thrown on error\r
+ */\r
+ public RowChangerImpl(\r
+ long heapConglom,\r
+ StaticCompiledOpenConglomInfo heapSCOCI,\r
+ DynamicCompiledOpenConglomInfo heapDCOCI,\r
+ IndexRowGenerator[] irgs,\r
+ long[] indexCIDS,\r
+ StaticCompiledOpenConglomInfo[] indexSCOCIs,\r
+ DynamicCompiledOpenConglomInfo[] indexDCOCIs,\r
+ int numberOfColumns,\r
+ int[] changedColumnIdsInput,\r
+ TransactionController tc,\r
+ FormatableBitSet baseRowReadList,\r
+ int[] baseRowReadMap,\r
+ Activation activation)\r
+ throws StandardException\r
+ {\r
+ this.heapConglom = heapConglom;\r
+ this.heapSCOCI = heapSCOCI;\r
+ this.heapDCOCI = heapDCOCI;\r
+ this.irgs = irgs;\r
+ this.indexCIDS = indexCIDS;\r
+ this.indexSCOCIs = indexSCOCIs;\r
+ this.indexDCOCIs = indexDCOCIs;\r
+ this.tc = tc;\r
+ this.baseRowReadList = baseRowReadList;\r
+ this.baseRowReadMap = baseRowReadMap;\r
+ this.activation = activation;\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(indexCIDS != null, "indexCIDS is null");\r
+ }\r
+\r
+ /*\r
+ ** Construct the update column FormatableBitSet.\r
+ ** It is 0 based as opposed to the 1 based\r
+ ** changed column ids.\r
+ */\r
+ if (changedColumnIdsInput != null)\r
+ {\r
+ /*\r
+ ** Sometimes replication does not have columns\r
+ ** in sorted order, and basically needs to\r
+ ** have the changed columns in non-sorted order.\r
+ ** So sort them first if needed.\r
+ */\r
+ changedColumnIds = RowUtil.inAscendingOrder(changedColumnIdsInput) ?\r
+ changedColumnIdsInput : sortArray(changedColumnIdsInput);\r
+\r
+ /*\r
+ ** Allocate the row array we are going to use during\r
+ ** update here, to avoid extra work. setup\r
+ ** the FormatableBitSet of columns being updated. See updateRow\r
+ ** for the use.\r
+ **\r
+ ** changedColumnIds is guaranteed to be in order, so just take\r
+ ** the last column number in the array to be the highest\r
+ ** column number.\r
+ */\r
+ sparseRowArray =\r
+ new DataValueDescriptor[changedColumnIds[changedColumnIds.length - 1] + 1];\r
+ changedColumnBitSet = new FormatableBitSet(numberOfColumns);\r
+ for (int i = 0; i < changedColumnIds.length; i++)\r
+ {\r
+ // make sure changedColumnBitSet can accomodate bit \r
+ // changedColumnIds[i] - 1 \r
+ changedColumnBitSet.grow(changedColumnIds[i]);\r
+ changedColumnBitSet.set(changedColumnIds[i] - 1);\r
+ }\r
+\r
+ /*\r
+ ** If we have a read map and a write map, we\r
+ ** need to have a way to map the changed column\r
+ ** ids to be relative to the read map.\r
+ */\r
+ if (baseRowReadList != null)\r
+ {\r
+ partialChangedColumnIds = new int[changedColumnIds.length];\r
+ int partialColumnNumber = 1;\r
+ int currentColumn = 0;\r
+ for (int i = 0; i < changedColumnIds.length; i++)\r
+ {\r
+ for (; currentColumn < changedColumnIds[i]; currentColumn++)\r
+ {\r
+ if (baseRowReadList.get(currentColumn))\r
+ {\r
+ partialColumnNumber++;\r
+ }\r
+ }\r
+ partialChangedColumnIds[i] = partialColumnNumber;\r
+ }\r
+ } \r
+ }\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(indexCIDS != null, "indexCIDS is null");\r
+ }\r
+ \r
+ }\r
+\r
+ /**\r
+ * Set the row holder for this changer to use.\r
+ * If the row holder is set, it wont bother \r
+ * saving copies of rows needed for deferred\r
+ * processing. Also, it will never close the\r
+ * passed in rowHolder.\r
+ *\r
+ * @param rowHolder the TemporaryRowHolder\r
+ */\r
+ public void setRowHolder(TemporaryRowHolder rowHolder)\r
+ {\r
+ this.rowHolder = (TemporaryRowHolderImpl)rowHolder;\r
+ }\r
+\r
+ /**\r
+ * @see RowChanger#setIndexNames\r
+ */\r
+ public void setIndexNames(String[] indexNames)\r
+ {\r
+ this.indexNames = indexNames;\r
+ }\r
+\r
+ /**\r
+ Open this RowChanger.\r
+\r
+ <P>Note to avoid the cost of fixing indexes that do not\r
+ change during update operations use openForUpdate().\r
+ @param lockMode The lock mode to use\r
+ (row or table, see TransactionController)\r
+\r
+ @exception StandardException thrown on failure to convert\r
+ */\r
+ public void open(int lockMode)\r
+ throws StandardException\r
+ {\r
+ open(lockMode, true);\r
+ }\r
+\r
+ /**\r
+ * @inheritDoc\r
+ */\r
+ public void open(int lockMode, boolean wait)\r
+ throws StandardException\r
+ {\r
+ //\r
+ //We open for update but say to fix every index on\r
+ //updates.\r
+ if (fixOnUpdate == null)\r
+ {\r
+ fixOnUpdate = new boolean[irgs.length];\r
+ for (int ix = 0; ix < irgs.length; ix++)\r
+ fixOnUpdate[ix] = true;\r
+ }\r
+ openForUpdate(fixOnUpdate, lockMode, wait);\r
+ }\r
+\r
+ /**\r
+ Open this RowChanger to avoid fixing indexes that do not change\r
+ during update operations. \r
+\r
+ @param fixOnUpdate fixOnUpdat[ix] == true ==> fix index 'ix' on\r
+ an update operation.\r
+ @param lockMode The lock mode to use\r
+ (row or table, see TransactionController)\r
+ @param wait If true, then the caller wants to wait for locks. False will be\r
+ when we using a nested user xaction - we want to timeout right away\r
+ if the parent holds the lock. (bug 4821)\r
+\r
+ @exception StandardException thrown on failure to convert\r
+ */\r
+ public void openForUpdate(\r
+ boolean[] fixOnUpdate, int lockMode, boolean wait\r
+ )\r
+ throws StandardException\r
+ {\r
+ LanguageConnectionContext lcc = null;\r
+\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT( ! isOpen, "RowChanger already open");\r
+ \r
+ if (activation != null)\r
+ {\r
+ lcc = activation.getLanguageConnectionContext();\r
+ }\r
+\r
+ /* Isolation level - translate from language to store */\r
+ int isolationLevel;\r
+ if (lcc == null)\r
+ {\r
+ isolationLevel = ExecutionContext.READ_COMMITTED_ISOLATION_LEVEL;\r
+ }\r
+ else\r
+ {\r
+ isolationLevel = lcc.getCurrentIsolationLevel();\r
+ }\r
+\r
+\r
+ switch (isolationLevel)\r
+ {\r
+ // Even though we preserve the isolation level at READ UNCOMMITTED,\r
+ // Store will overwrite it to READ COMMITTED for update.\r
+ case ExecutionContext.READ_UNCOMMITTED_ISOLATION_LEVEL:\r
+ isolationLevel = \r
+ TransactionController.ISOLATION_READ_UNCOMMITTED;\r
+ break;\r
+\r
+ case ExecutionContext.READ_COMMITTED_ISOLATION_LEVEL:\r
+ isolationLevel = \r
+ TransactionController.ISOLATION_READ_COMMITTED;\r
+ break;\r
+\r
+ case ExecutionContext.REPEATABLE_READ_ISOLATION_LEVEL:\r
+ isolationLevel = \r
+ TransactionController.ISOLATION_REPEATABLE_READ;\r
+ break;\r
+\r
+ case ExecutionContext.SERIALIZABLE_ISOLATION_LEVEL:\r
+ isolationLevel = \r
+ TransactionController.ISOLATION_SERIALIZABLE;\r
+ break;\r
+\r
+ default:\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.THROWASSERT(\r
+ "Invalid isolation level - " + isolationLevel);\r
+ }\r
+ }\r
+\r
+ try {\r
+\r
+ /* We can get called by either an activation or \r
+ * the DataDictionary. The DD cannot use the\r
+ * CompiledInfo while the activation can.\r
+ */\r
+ if (heapSCOCI != null)\r
+ {\r
+ baseCC =\r
+ tc.openCompiledConglomerate(\r
+ false,\r
+ (TransactionController.OPENMODE_FORUPDATE |\r
+ ((wait) ? 0 : TransactionController.OPENMODE_LOCK_NOWAIT)),\r
+ lockMode,\r
+ isolationLevel,\r
+ heapSCOCI,\r
+ heapDCOCI);\r
+ }\r
+ else\r
+ {\r
+ baseCC =\r
+ tc.openConglomerate(\r
+ heapConglom,\r
+ false,\r
+ (TransactionController.OPENMODE_FORUPDATE |\r
+ ((wait) ? 0 : TransactionController.OPENMODE_LOCK_NOWAIT)),\r
+ lockMode,\r
+ isolationLevel);\r
+ }\r
+\r
+ } catch (StandardException se) {\r
+ if (activation != null)\r
+ activation.checkStatementValidity();\r
+ throw se;\r
+ }\r
+\r
+ /* Save the ConglomerateController off in the activation\r
+ * to eliminate the need to open it a 2nd time if we are doing\r
+ * and index to base row for the search as part of an update or\r
+ * delete below us.\r
+ * NOTE: activation can be null. (We don't have it in\r
+ * the DataDictionary.)\r
+ */\r
+ if (activation != null)\r
+ {\r
+ activation.checkStatementValidity();\r
+ activation.setHeapConglomerateController(baseCC);\r
+ }\r
+\r
+ /* Only worry about indexes if there are indexes to worry about */\r
+ if (indexCIDS.length != 0)\r
+ {\r
+ /* IndexSetChanger re-used across executions. */\r
+ if (isc == null)\r
+ {\r
+ isc = new IndexSetChanger(irgs,\r
+ indexCIDS,\r
+ indexSCOCIs,\r
+ indexDCOCIs,\r
+ indexNames,\r
+ baseCC,\r
+ tc,\r
+ lockMode,\r
+ baseRowReadList,\r
+ isolationLevel,\r
+ activation\r
+ );\r
+ isc.setRowHolder(rowHolder);\r
+ }\r
+ else\r
+ {\r
+\r
+ /* Propagate the heap's ConglomerateController to\r
+ * all of the underlying index changers.\r
+ */\r
+ isc.setBaseCC(baseCC);\r
+ }\r
+\r
+ isc.open(fixOnUpdate);\r
+\r
+ if (baseRowLocation == null)\r
+ baseRowLocation = baseCC.newRowLocationTemplate();\r
+ }\r
+\r
+ isOpen = true;\r
+ }\r
+ \r
+ /**\r
+ Insert a row into the table and perform associated index maintenance.\r
+\r
+ @param baseRow the row.\r
+ @exception StandardException Thrown on error\r
+ */\r
+ public void insertRow(ExecRow baseRow)\r
+ throws StandardException\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(! baseCC.isKeyed(),\r
+ "Keyed inserts not yet supported");\r
+\r
+ if (baseCC.isKeyed())\r
+ {\r
+ //kcc.insert(row.key(), row());\r
+ }\r
+ else\r
+ {\r
+ if (isc != null)\r
+ {\r
+ baseCC.insertAndFetchLocation(baseRow.getRowArray(), baseRowLocation);\r
+ isc.insert(baseRow, baseRowLocation);\r
+ }\r
+ else\r
+ {\r
+ baseCC.insert(baseRow.getRowArray());\r
+ }\r
+ }\r
+ }\r
+\r
+ \r
+ /**\r
+ Delete a row from the table and perform associated index maintenance.\r
+\r
+ @param baseRow the row.\r
+ @param baseRowLocation the row's base conglomerate\r
+ location\r
+ @exception StandardException Thrown on error\r
+ */\r
+ public void deleteRow(ExecRow baseRow, RowLocation baseRowLocation)\r
+ throws StandardException\r
+ {\r
+ if (isc != null)\r
+ {\r
+ isc.delete(baseRow, baseRowLocation);\r
+ }\r
+ baseCC.delete(baseRowLocation);\r
+ }\r
+\r
+ /**\r
+ Update a row in the table and perform associated index maintenance.\r
+\r
+ @param oldBaseRow the old image of the row.\r
+ @param newBaseRow the new image of the row.\r
+ @param baseRowLocation the row's base conglomerate\r
+ location\r
+ @exception StandardException Thrown on error\r
+ */\r
+ public void updateRow(ExecRow oldBaseRow,\r
+ ExecRow newBaseRow,\r
+ RowLocation baseRowLocation)\r
+ throws StandardException\r
+ {\r
+ if (isc != null)\r
+ {\r
+ isc.update(oldBaseRow, newBaseRow, baseRowLocation);\r
+ }\r
+\r
+ if (changedColumnBitSet != null)\r
+ {\r
+ DataValueDescriptor[] baseRowArray = newBaseRow.getRowArray();\r
+ int[] changedColumnArray = (partialChangedColumnIds == null) ?\r
+ changedColumnIds : partialChangedColumnIds;\r
+ int nextColumnToUpdate = -1;\r
+ for (int i = 0; i < changedColumnArray.length; i++)\r
+ {\r
+ int copyFrom = changedColumnArray[i] - 1;\r
+ nextColumnToUpdate =\r
+ changedColumnBitSet.anySetBit(nextColumnToUpdate);\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(nextColumnToUpdate >= 0,\r
+ "More columns in changedColumnArray than in changedColumnBitSet");\r
+ }\r
+ sparseRowArray[nextColumnToUpdate] = baseRowArray[copyFrom];\r
+ }\r
+ }\r
+ else\r
+ {\r
+ sparseRowArray = newBaseRow.getRowArray();\r
+ }\r
+ baseCC.replace(baseRowLocation, \r
+ sparseRowArray, \r
+ changedColumnBitSet);\r
+ }\r
+\r
+ /**\r
+ Finish processing the changes. This means applying the deferred\r
+ inserts for updates to unique indexes.\r
+\r
+ @exception StandardException Thrown on error\r
+ */\r
+ public void finish()\r
+ throws StandardException\r
+ {\r
+ if (isc != null)\r
+ {\r
+ isc.finish();\r
+ }\r
+ }\r
+\r
+ /**\r
+ Close this RowChanger.\r
+\r
+ @exception StandardException Thrown on error\r
+ */\r
+ public void close()\r
+ throws StandardException\r
+ {\r
+ //\r
+ //NOTE: isc uses baseCC. Since we close baseCC we free isc for now.\r
+ //We could consider making isc open its own baseCC or even leaving\r
+ //baseCC open to promote re-use. We must keep in mind that baseCC\r
+ //is associated with the opener's TransactionController.\r
+ if (isc != null)\r
+ {\r
+ isc.close(); \r
+ }\r
+\r
+ if (baseCC != null)\r
+ {\r
+ if (activation == null || activation.getForUpdateIndexScan() == null)\r
+ baseCC.close(); //beetle 3865, don't close if borrowed to cursor\r
+ baseCC = null;\r
+ }\r
+ \r
+ isOpen = false;\r
+\r
+ // rowHolder is reused across executions and closed by caller\r
+ // since caller creates it\r
+\r
+ if (activation != null)\r
+ {\r
+ activation.clearHeapConglomerateController();\r
+ }\r
+ }\r
+\r
+ /** @see RowChanger#getHeapConglomerateController */\r
+ public ConglomerateController getHeapConglomerateController()\r
+ {\r
+ return baseCC;\r
+ }\r
+\r
+ private int[] sortArray(int[] input)\r
+ {\r
+ /*\r
+ ** Sotring.sort() will change the underlying array, so we\r
+ ** 'clone' it first\r
+ */\r
+ int[] output = new int[input.length];\r
+ System.arraycopy(input, 0, output, 0, input.length);\r
+ java.util.Arrays.sort(output);\r
+ return output;\r
+ }\r
+}\r