--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.execute.IndexChanger\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.Properties;\r
+\r
+import org.apache.derby.catalog.UUID;\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.services.i18n.MessageService;\r
+import org.apache.derby.iapi.services.io.FormatableBitSet;\r
+import org.apache.derby.iapi.services.monitor.Monitor;\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.conn.LanguageConnectionContext;\r
+import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.ConstraintDescriptor;\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.execute.CursorResultSet;\r
+import org.apache.derby.iapi.sql.execute.ExecIndexRow;\r
+import org.apache.derby.iapi.sql.execute.ExecRow;\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.ScanController;\r
+import org.apache.derby.iapi.store.access.StaticCompiledOpenConglomInfo;\r
+import org.apache.derby.iapi.store.access.TransactionController;\r
+import org.apache.derby.iapi.types.DataValueDescriptor;\r
+import org.apache.derby.iapi.types.RowLocation;\r
+\r
+/**\r
+ Perform Index maintenace associated with DML operations for a single index.\r
+ */\r
+public class IndexChanger\r
+{\r
+ private IndexRowGenerator irg;\r
+ //Index Conglomerate ID\r
+ private long indexCID;\r
+ private DynamicCompiledOpenConglomInfo indexDCOCI;\r
+ private StaticCompiledOpenConglomInfo indexSCOCI;\r
+ private String indexName;\r
+ private ConglomerateController baseCC;\r
+ private TransactionController tc;\r
+ private int lockMode;\r
+ private FormatableBitSet baseRowReadMap;\r
+\r
+ private ConglomerateController indexCC = null;\r
+ private ScanController indexSC = null;\r
+\r
+ private LanguageConnectionContext lcc;\r
+\r
+ //\r
+ //Index rows used by this module to perform DML.\r
+ private ExecIndexRow ourIndexRow = null;\r
+ private ExecIndexRow ourUpdatedIndexRow = null;\r
+\r
+ private TemporaryRowHolderImpl rowHolder = null;\r
+ private boolean rowHolderPassedIn;\r
+ private int isolationLevel;\r
+ private Activation activation;\r
+ private boolean ownIndexSC = true;\r
+\r
+ /**\r
+ Create an IndexChanger\r
+\r
+ @param irg the IndexRowGenerator for the index.\r
+ @param indexCID the conglomerate id for the index.\r
+ @param indexSCOCI the SCOCI for the idexes. \r
+ @param indexDCOCI the DCOCI for the idexes. \r
+ @param baseCC the ConglomerateController for the base table.\r
+ @param tc The TransactionController\r
+ @param lockMode The lock mode (granularity) to use\r
+ @param baseRowReadMap Map of columns read in. 1 based.\r
+ @param isolationLevel Isolation level to use.\r
+ @param activation Current activation\r
+\r
+ @exception StandardException Thrown on error\r
+ */\r
+ public IndexChanger\r
+ (\r
+ IndexRowGenerator irg,\r
+ long indexCID,\r
+ StaticCompiledOpenConglomInfo indexSCOCI,\r
+ DynamicCompiledOpenConglomInfo indexDCOCI,\r
+ String indexName,\r
+ ConglomerateController baseCC,\r
+ TransactionController tc,\r
+ int lockMode,\r
+ FormatableBitSet baseRowReadMap,\r
+ int isolationLevel,\r
+ Activation activation\r
+ )\r
+ throws StandardException\r
+ {\r
+ this.irg = irg;\r
+ this.indexCID = indexCID;\r
+ this.indexSCOCI = indexSCOCI;\r
+ this.indexDCOCI = indexDCOCI;\r
+ this.baseCC = baseCC;\r
+ this.tc = tc;\r
+ this.lockMode = lockMode;\r
+ this.baseRowReadMap = baseRowReadMap;\r
+ this.rowHolderPassedIn = false;\r
+ this.isolationLevel = isolationLevel;\r
+ this.activation = activation;\r
+ this.indexName = indexName;\r
+\r
+ // activation will be null when called from DataDictionary\r
+ if (activation != null && activation.getIndexConglomerateNumber() == indexCID)\r
+ {\r
+ ownIndexSC = false;\r
+ }\r
+ \r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(tc != null, \r
+ "TransactionController argument to constructor is null");\r
+ SanityManager.ASSERT(irg != null, \r
+ "IndexRowGenerator argument to constructor is null");\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 row holder\r
+ */\r
+ public void setRowHolder(TemporaryRowHolderImpl rowHolder)\r
+ {\r
+ this.rowHolder = rowHolder;\r
+ rowHolderPassedIn = (rowHolder != null);\r
+ }\r
+\r
+ /**\r
+ * Propagate the heap's ConglomerateController to\r
+ * this index changer.\r
+ *\r
+ * @param baseCC The heap's ConglomerateController.\r
+ */\r
+ public void setBaseCC(ConglomerateController baseCC)\r
+ {\r
+ this.baseCC = baseCC;\r
+ }\r
+\r
+ /**\r
+ Set the column values for 'ourIndexRow' to refer to \r
+ a base table row and location provided by the caller.\r
+ The idea here is to \r
+ @param baseRow a base table row.\r
+ @param baseRowLoc baseRowLoc baseRow's location\r
+ @exception StandardException Thrown on error\r
+ */\r
+ private void setOurIndexRow(ExecRow baseRow,\r
+ RowLocation baseRowLoc)\r
+ throws StandardException\r
+ {\r
+ if (ourIndexRow == null)\r
+ ourIndexRow = irg.getIndexRowTemplate();\r
+ \r
+ irg.getIndexRow(baseRow, baseRowLoc, ourIndexRow, baseRowReadMap);\r
+ }\r
+\r
+ /**\r
+ Set the column values for 'ourUpdatedIndexRow' to refer to \r
+ a base table row and location provided by the caller.\r
+ The idea here is to \r
+ @param baseRow a base table row.\r
+ @param baseRowLoc baseRowLoc baseRow's location\r
+ @exception StandardException Thrown on error\r
+ */\r
+ private void setOurUpdatedIndexRow(ExecRow baseRow,\r
+ RowLocation baseRowLoc)\r
+ throws StandardException\r
+ {\r
+ if (ourUpdatedIndexRow == null)\r
+ ourUpdatedIndexRow = irg.getIndexRowTemplate();\r
+ \r
+ irg.getIndexRow(baseRow, baseRowLoc, ourUpdatedIndexRow, baseRowReadMap);\r
+ }\r
+\r
+ /**\r
+ * Determine whether or not any columns in the current index\r
+ * row are being changed by the update. No need to update the\r
+ * index if no columns changed.\r
+ *\r
+ * @return Nothing.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ private boolean indexRowChanged()\r
+ throws StandardException\r
+ {\r
+ int numColumns = ourIndexRow.nColumns();\r
+ for (int index = 1; index <= numColumns; index++)\r
+ {\r
+ DataValueDescriptor oldOrderable = ourIndexRow.getColumn(index);\r
+ DataValueDescriptor newOrderable = ourUpdatedIndexRow.getColumn(index);\r
+ if (! (oldOrderable.compare(DataValueDescriptor.ORDER_OP_EQUALS, newOrderable, true, true)))\r
+ {\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+\r
+ private ExecIndexRow getDeferredIndexRowTemplate(ExecRow baseRow,\r
+ RowLocation baseRowLoc)\r
+ throws StandardException\r
+ {\r
+ ExecIndexRow template;\r
+\r
+ template = irg.getIndexRowTemplate();\r
+\r
+ irg.getIndexRow(baseRow, baseRowLoc, template, baseRowReadMap);\r
+\r
+ return template;\r
+ }\r
+\r
+ /**\r
+ Position our index scan to 'ourIndexRow'.\r
+\r
+ <P>This creates the scan the first time it is called.\r
+\r
+ @exception StandardException Thrown on error\r
+ */\r
+ private void setScan()\r
+ throws StandardException\r
+ {\r
+ /* Get the SC from the activation if re-using */\r
+ if (! ownIndexSC)\r
+ {\r
+ indexSC = activation.getIndexScanController();\r
+ }\r
+ else if (indexSC == null)\r
+ {\r
+ RowLocation templateBaseRowLocation = baseCC.newRowLocationTemplate();\r
+ /* DataDictionary doesn't have compiled info */\r
+ if (indexSCOCI == null)\r
+ {\r
+ indexSC = \r
+ tc.openScan(\r
+ indexCID,\r
+ false, /* hold */\r
+ TransactionController.OPENMODE_FORUPDATE, /* forUpdate */\r
+ lockMode,\r
+ isolationLevel,\r
+ (FormatableBitSet)null, /* all fields */\r
+ ourIndexRow.getRowArray(), /* startKeyValue */\r
+ ScanController.GE, /* startSearchOp */\r
+ null, /* qualifier */\r
+ ourIndexRow.getRowArray(), /* stopKeyValue */\r
+ ScanController.GT /* stopSearchOp */\r
+ );\r
+ }\r
+ else\r
+ {\r
+ indexSC = \r
+ tc.openCompiledScan(\r
+ false, /* hold */\r
+ TransactionController.OPENMODE_FORUPDATE, /* forUpdate */\r
+ lockMode,\r
+ isolationLevel,\r
+ (FormatableBitSet)null, /* all fields */\r
+ ourIndexRow.getRowArray(), /* startKeyValue */\r
+ ScanController.GE, /* startSearchOp */\r
+ null, /* qualifier */\r
+ ourIndexRow.getRowArray(), /* stopKeyValue */\r
+ ScanController.GT, /* stopSearchOp */\r
+ indexSCOCI,\r
+ indexDCOCI\r
+ );\r
+ }\r
+ }\r
+ else\r
+ {\r
+ indexSC.reopenScan(\r
+ ourIndexRow.getRowArray(), /* startKeyValue */\r
+ ScanController.GE, /* startSearchOperator */\r
+ null, /* qualifier */\r
+ ourIndexRow.getRowArray(), /* stopKeyValue */\r
+ ScanController.GT /* stopSearchOperator */\r
+ );\r
+ }\r
+ }\r
+\r
+ /**\r
+ Close our index Conglomerate Controller\r
+ */\r
+ private void closeIndexCC()\r
+ throws StandardException\r
+ {\r
+ if (indexCC != null)\r
+ indexCC.close();\r
+ indexCC = null;\r
+ }\r
+\r
+ /**\r
+ Close our index ScanController.\r
+ */\r
+ private void closeIndexSC()\r
+ throws StandardException\r
+ {\r
+ /* Only consider closing index SC if we own it. */\r
+ if (ownIndexSC && indexSC != null)\r
+ {\r
+ indexSC.close();\r
+ indexSC = null;\r
+ }\r
+ }\r
+\r
+ /**\r
+ Delete a row from our index. This assumes our index ScanController\r
+ is positioned before the row by setScan if we own the SC, otherwise\r
+ it is positioned on the row by the underlying index scan.\r
+ \r
+ <P>This verifies the row exists and is unique.\r
+ \r
+ @exception StandardException Thrown on error\r
+ */\r
+ private void doDelete()\r
+ throws StandardException\r
+ {\r
+ if (ownIndexSC)\r
+ {\r
+ if (! indexSC.next())\r
+ {\r
+ // This means that the entry for the index does not exist, this\r
+ // is a serious problem with the index. Past fixed problems\r
+ // like track 3703 can leave db's in the field with this problem\r
+ // even though the bug in the code which caused it has long \r
+ // since been fixed. Then the problem can surface months later\r
+ // when the customer attempts to upgrade. By "ignoring" the\r
+ // missing row here the problem is automatically "fixed" and\r
+ // since the code is trying to delete the row anyway it doesn't\r
+ // seem like such a bad idea. It also then gives a tool to \r
+ // support to be able to fix some system catalog problems where\r
+ // they can delete the base rows by dropping the system objects\r
+ // like stored statements.\r
+\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.THROWASSERT(\r
+ "Index row "+RowUtil.toString(ourIndexRow)+\r
+ " not found in conglomerateid " + indexCID +\r
+ "Current scan = " + indexSC);\r
+\r
+ Object[] args = new Object[2];\r
+ args[0] = ourIndexRow.getRowArray()[ourIndexRow.getRowArray().length - 1];\r
+ args[1] = new Long(indexCID);\r
+\r
+ Monitor.getStream().println(MessageService.getCompleteMessage(\r
+ SQLState.LANG_IGNORE_MISSING_INDEX_ROW_DURING_DELETE, \r
+ args));\r
+\r
+ // just return indicating the row has been deleted.\r
+ return;\r
+ }\r
+ }\r
+\r
+ indexSC.delete();\r
+ }\r
+\r
+ /**\r
+ Insert a row into our indes.\r
+ \r
+ <P>This opens our index ConglomeratController the first time it\r
+ is called. \r
+ \r
+ @exception StandardException Thrown on error\r
+ */\r
+ private void doInsert()\r
+ throws StandardException\r
+ {\r
+ insertAndCheckDups(ourIndexRow);\r
+ }\r
+\r
+ /**\r
+ Insert a row into the temporary conglomerate\r
+ \r
+ <P>This opens our deferred ConglomeratController the first time it\r
+ is called.\r
+ \r
+ @exception StandardException Thrown on error\r
+ */\r
+ private void doDeferredInsert()\r
+ throws StandardException\r
+ {\r
+ if (rowHolder == null)\r
+ {\r
+ Properties properties = new Properties();\r
+\r
+ // Get the properties on the index\r
+ openIndexCC().getInternalTablePropertySet(properties);\r
+\r
+ /*\r
+ ** Create our row holder. it is ok to skip passing\r
+ ** in the result description because if we don't already\r
+ ** have a row holder, then we are the only user of the\r
+ ** row holder (the description is needed when the row\r
+ ** holder is going to be handed to users for triggers).\r
+ */\r
+ rowHolder = new TemporaryRowHolderImpl(activation, properties,\r
+ (ResultDescription) null);\r
+ }\r
+\r
+ /*\r
+ ** If the user of the IndexChanger already\r
+ ** had a row holder, then we don't need to\r
+ ** bother saving deferred inserts -- they\r
+ ** have already done so. \r
+ */\r
+ if (!rowHolderPassedIn)\r
+ {\r
+ rowHolder.insert(ourIndexRow);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Insert the given row into the given conglomerate and check for duplicate\r
+ * key error.\r
+ *\r
+ * @param row The row to insert\r
+ *\r
+ * @exception StandardException Thrown on duplicate key error\r
+ */\r
+ private void insertAndCheckDups(ExecIndexRow row)\r
+ throws StandardException\r
+ {\r
+ openIndexCC();\r
+\r
+ int insertStatus = indexCC.insert(row.getRowArray());\r
+\r
+ if (insertStatus == ConglomerateController.ROWISDUPLICATE)\r
+ {\r
+ /*\r
+ ** We have a duplicate key error. \r
+ */\r
+ String indexOrConstraintName = indexName;\r
+ // now get table name, and constraint name if needed\r
+ LanguageConnectionContext lcc =\r
+ activation.getLanguageConnectionContext();\r
+ DataDictionary dd = lcc.getDataDictionary();\r
+ //get the descriptors\r
+ ConglomerateDescriptor cd = dd.getConglomerateDescriptor(indexCID);\r
+\r
+ UUID tableID = cd.getTableID();\r
+ TableDescriptor td = dd.getTableDescriptor(tableID);\r
+ String tableName = td.getName();\r
+ \r
+ if (indexOrConstraintName == null) // no index name passed in\r
+ {\r
+ ConstraintDescriptor conDesc = dd.getConstraintDescriptor(td,\r
+ cd.getUUID());\r
+ indexOrConstraintName = conDesc.getConstraintName();\r
+ } \r
+\r
+ StandardException se = \r
+ StandardException.newException(\r
+ SQLState.LANG_DUPLICATE_KEY_CONSTRAINT, indexOrConstraintName, tableName);\r
+ throw se;\r
+ }\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (insertStatus != 0)\r
+ {\r
+ SanityManager.THROWASSERT("Unknown insert status " + insertStatus);\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Open the ConglomerateController for this index if it isn't open yet.\r
+ *\r
+ * @return The ConglomerateController for this index.\r
+ *\r
+ * @exception StandardException Thrown on duplicate key error\r
+ */\r
+ private ConglomerateController openIndexCC()\r
+ throws StandardException\r
+ {\r
+ if (indexCC == null)\r
+ {\r
+ /* DataDictionary doesn't have compiled info */\r
+ if (indexSCOCI == null)\r
+ {\r
+ indexCC = \r
+ tc.openConglomerate(\r
+ indexCID,\r
+ false,\r
+ (TransactionController.OPENMODE_FORUPDATE |\r
+ TransactionController.OPENMODE_BASEROW_INSERT_LOCKED),\r
+ lockMode,\r
+ isolationLevel);\r
+ }\r
+ else\r
+ {\r
+ indexCC = \r
+ tc.openCompiledConglomerate(\r
+ false,\r
+ (TransactionController.OPENMODE_FORUPDATE |\r
+ TransactionController.OPENMODE_BASEROW_INSERT_LOCKED),\r
+ lockMode,\r
+ isolationLevel,\r
+ indexSCOCI,\r
+ indexDCOCI);\r
+ }\r
+ }\r
+\r
+ return indexCC;\r
+ }\r
+\r
+ /**\r
+ Open this IndexChanger.\r
+\r
+ @exception StandardException Thrown on error\r
+ */\r
+ public void open()\r
+ throws StandardException\r
+ {\r
+ }\r
+\r
+ /**\r
+ Perform index maintenance to support a delete of a base table row.\r
+\r
+ @param baseRow the base table row.\r
+ @param baseRowLocation the base table row's location.\r
+ @exception StandardException Thrown on error\r
+ */\r
+ public void delete(ExecRow baseRow,\r
+ RowLocation baseRowLocation)\r
+ throws StandardException\r
+ {\r
+ setOurIndexRow(baseRow, baseRowLocation);\r
+ setScan();\r
+ doDelete();\r
+ }\r
+\r
+ /**\r
+ Perform index maintenance to support an update of a base table row.\r
+\r
+ @param oldBaseRow the old image of the base table row.\r
+ @param newBaseRow the new image of the base table row.\r
+ @param baseRowLocation the base table row's location.\r
+\r
+ @exception StandardException Thrown on error\r
+ */\r
+ public void update(ExecRow oldBaseRow,\r
+ ExecRow newBaseRow,\r
+ RowLocation baseRowLocation\r
+ )\r
+ throws StandardException\r
+ {\r
+ setOurIndexRow(oldBaseRow, baseRowLocation);\r
+ setOurUpdatedIndexRow(newBaseRow, baseRowLocation);\r
+\r
+ /* We skip the update in the degenerate case\r
+ * where none of the key columns changed.\r
+ * (From an actual customer case.)\r
+ */\r
+ if (indexRowChanged())\r
+ {\r
+ setScan();\r
+ doDelete();\r
+ insertForUpdate(newBaseRow, baseRowLocation);\r
+ }\r
+ }\r
+\r
+ /**\r
+ Perform index maintenance to support an insert of a base table row.\r
+\r
+ @param newRow the base table row.\r
+ @param baseRowLocation the base table row's location.\r
+\r
+ @exception StandardException Thrown on error\r
+ */\r
+ public void insert(ExecRow newRow, RowLocation baseRowLocation)\r
+ throws StandardException\r
+ {\r
+ setOurIndexRow(newRow, baseRowLocation);\r
+ doInsert();\r
+ }\r
+\r
+ /**\r
+ If we're updating a unique index, the inserts have to be\r
+ deferred. This is to avoid uniqueness violations that are only\r
+ temporary. If we do all the deletes first, only "true" uniqueness\r
+ violations can happen. We do this here, rather than in open(),\r
+ because this is the only operation that requires deferred inserts,\r
+ and we only want to create the conglomerate if necessary.\r
+\r
+ @param newRow the base table row.\r
+ @param baseRowLocation the base table row's location.\r
+\r
+ @exception StandardException Thrown on error\r
+ */\r
+ void insertForUpdate(ExecRow newRow, RowLocation baseRowLocation)\r
+ throws StandardException\r
+ {\r
+ setOurIndexRow(newRow, baseRowLocation);\r
+\r
+ if (irg.isUnique())\r
+ {\r
+ doDeferredInsert();\r
+ }\r
+ else\r
+ {\r
+ doInsert();\r
+ }\r
+ }\r
+\r
+ /**\r
+ Finish doing the changes for this index. This is intended for deferred\r
+ inserts for unique indexes. It has no effect unless we are doing an\r
+ update of a unique index.\r
+\r
+ @exception StandardException Thrown on error\r
+ */\r
+ public void finish()\r
+ throws StandardException\r
+ {\r
+ ExecRow deferredRow;\r
+ ExecIndexRow deferredIndexRow = new IndexRow();\r
+\r
+ /* Deferred processing only necessary for unique indexes */\r
+ if (rowHolder != null)\r
+ {\r
+ CursorResultSet rs = rowHolder.getResultSet();\r
+ try\r
+ {\r
+ rs.open();\r
+ while ((deferredRow = rs.getNextRow()) != null)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (!(deferredRow instanceof ExecIndexRow))\r
+ {\r
+ SanityManager.THROWASSERT("deferredRow isn't an instance "+\r
+ "of ExecIndexRow as expected. "+\r
+ "It is an "+deferredRow.getClass().getName());\r
+ }\r
+ }\r
+ insertAndCheckDups((ExecIndexRow)deferredRow);\r
+ }\r
+ }\r
+ finally\r
+ {\r
+ rs.close();\r
+\r
+ /*\r
+ ** If row holder was passed in, let the\r
+ ** client of this method clean it up.\r
+ */\r
+ if (!rowHolderPassedIn)\r
+ {\r
+ rowHolder.close();\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ Close this IndexChanger.\r
+\r
+ @exception StandardException Thrown on error\r
+ */\r
+ public void close()\r
+ throws StandardException\r
+ {\r
+ closeIndexCC();\r
+ closeIndexSC();\r
+ if (rowHolder != null && !rowHolderPassedIn)\r
+ {\r
+ rowHolder.close();\r
+ }\r
+ baseCC = null;\r
+ }\r
+}\r