--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.iapi.db.ConsistencyChecker\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.iapi.db;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.error.PublicAPI;\r
+\r
+import org.apache.derby.iapi.sql.dictionary.DataDictionary;\r
+import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.TableDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.ColumnDescriptorList;\r
+import org.apache.derby.iapi.sql.dictionary.ConstraintDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.ConstraintDescriptorList;\r
+import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor;\r
+\r
+import org.apache.derby.iapi.sql.execute.ExecRow;\r
+import org.apache.derby.iapi.sql.execute.ExecutionFactory;\r
+\r
+import org.apache.derby.iapi.types.DataValueDescriptor;\r
+import org.apache.derby.iapi.types.DataValueFactory;\r
+\r
+\r
+import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;\r
+import org.apache.derby.iapi.sql.conn.ConnectionUtil;\r
+\r
+import org.apache.derby.iapi.store.access.TransactionController;\r
+import org.apache.derby.iapi.types.RowLocation;\r
+import org.apache.derby.iapi.store.access.ScanController;\r
+import org.apache.derby.iapi.store.access.ConglomerateController;\r
+import org.apache.derby.iapi.store.access.RowUtil;\r
+\r
+import org.apache.derby.iapi.reference.SQLState;\r
+\r
+import org.apache.derby.iapi.services.io.FormatableBitSet;\r
+\r
+import java.sql.SQLException;\r
+\r
+/**\r
+ * The ConsistencyChecker class provides static methods for verifying\r
+ * the consistency of the data stored within a database.\r
+ * \r
+ *\r
+ <p>This class can only be used within an SQL-J statement, a Java procedure or a server side Java method.\r
+ <p>This class can be accessed using the class alias <code> CONSISTENCYCHECKER </code> in SQL-J statements.\r
+ */\r
+public class ConsistencyChecker\r
+{\r
+\r
+ /** no requirement for a constructor */\r
+ private ConsistencyChecker() {\r
+ }\r
+\r
+ /**\r
+ * Check the named table, ensuring that all of its indexes are consistent\r
+ * with the base table.\r
+ * Use this\r
+ * method only within an SQL-J statement; do not call it directly.\r
+ * <P>When tables are consistent, the method returns true. Otherwise, the method throws an exception.\r
+ * <p>To check the consistency of a single table:\r
+ * <p><code>\r
+ * VALUES ConsistencyChecker::checkTable(<i>SchemaName</i>, <i>TableName</i>)</code></p>\r
+ * <P>For example, to check the consistency of the table <i>APP.Flights</i>:\r
+ * <p><code>\r
+ * VALUES ConsistencyChecker::checkTable('APP', 'FLIGHTS')</code></p>\r
+ * <p>To check the consistency of all of the tables in the 'APP' schema,\r
+ * stopping at the first failure: \r
+ *\r
+ * <P><code>SELECT tablename, ConsistencyChecker::checkTable(<br>\r
+ * 'APP', tablename)<br>\r
+ * FROM sys.sysschemas s, sys.systables t\r
+ * WHERE s.schemaname = 'APP' AND s.schemaid = t.schemaid</code>\r
+ *\r
+ * <p> To check the consistency of an entire database, stopping at the first failure:\r
+ *\r
+ * <p><code>SELECT schemaname, tablename,<br>\r
+ * ConsistencyChecker::checkTable(schemaname, tablename)<br>\r
+ * FROM sys.sysschemas s, sys.systables t<br>\r
+ * WHERE s.schemaid = t.schemaid</code>\r
+ *\r
+ *\r
+ *\r
+ * @param schemaName The schema name of the table.\r
+ * @param tableName The name of the table\r
+ *\r
+ * @return true, if the table is consistent, exception thrown if inconsistent\r
+ *\r
+ * @exception SQLException Thrown if some inconsistency\r
+ * is found, or if some unexpected\r
+ * exception is thrown..\r
+ */\r
+ public static boolean checkTable(String schemaName, String tableName)\r
+ throws SQLException\r
+ {\r
+ DataDictionary dd;\r
+ TableDescriptor td;\r
+ long baseRowCount = -1;\r
+ TransactionController tc;\r
+ ConglomerateDescriptor heapCD;\r
+ ConglomerateDescriptor indexCD;\r
+ ExecRow baseRow;\r
+ ExecRow indexRow;\r
+ RowLocation rl = null;\r
+ RowLocation scanRL = null;\r
+ ScanController scan = null;\r
+ int[] baseColumnPositions;\r
+ int baseColumns = 0;\r
+ DataValueFactory dvf;\r
+ long indexRows;\r
+ ConglomerateController baseCC = null;\r
+ ConglomerateController indexCC = null;\r
+ SchemaDescriptor sd;\r
+ ConstraintDescriptor constraintDesc;\r
+\r
+ LanguageConnectionContext lcc = ConnectionUtil.getCurrentLCC();\r
+ tc = lcc.getTransactionExecute();\r
+\r
+ try {\r
+\r
+ dd = lcc.getDataDictionary();\r
+\r
+ dvf = lcc.getDataValueFactory();\r
+ \r
+ ExecutionFactory ef = lcc.getLanguageConnectionFactory().getExecutionFactory();\r
+\r
+ sd = dd.getSchemaDescriptor(schemaName, tc, true);\r
+ td = dd.getTableDescriptor(tableName, sd);\r
+\r
+ if (td == null)\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.LANG_TABLE_NOT_FOUND, \r
+ schemaName + "." + tableName);\r
+ }\r
+\r
+ /* Skip views */\r
+ if (td.getTableType() == TableDescriptor.VIEW_TYPE)\r
+ {\r
+ return true;\r
+ }\r
+\r
+ /* Open the heap for reading */\r
+ baseCC = tc.openConglomerate(\r
+ td.getHeapConglomerateId(), false, 0, \r
+ TransactionController.MODE_TABLE,\r
+ TransactionController.ISOLATION_SERIALIZABLE);\r
+\r
+ /* Check the consistency of the heap */\r
+ baseCC.checkConsistency();\r
+\r
+ heapCD = td.getConglomerateDescriptor(td.getHeapConglomerateId());\r
+\r
+ /* Get a row template for the base table */\r
+ baseRow = ef.getValueRow(td.getNumberOfColumns());\r
+\r
+ /* Fill the row with nulls of the correct type */\r
+ ColumnDescriptorList cdl = td.getColumnDescriptorList();\r
+ int cdlSize = cdl.size();\r
+\r
+ for (int index = 0; index < cdlSize; index++)\r
+ {\r
+ ColumnDescriptor cd = (ColumnDescriptor) cdl.elementAt(index);\r
+ baseRow.setColumn(cd.getPosition(),\r
+ cd.getType().getNull());\r
+ }\r
+\r
+ /* Look at all the indexes on the table */\r
+ ConglomerateDescriptor[] cds = td.getConglomerateDescriptors();\r
+ for (int index = 0; index < cds.length; index++)\r
+ {\r
+ indexCD = cds[index];\r
+ /* Skip the heap */\r
+ if ( ! indexCD.isIndex())\r
+ continue;\r
+\r
+ /* Check the internal consistency of the index */\r
+ indexCC = \r
+ tc.openConglomerate(\r
+ indexCD.getConglomerateNumber(),\r
+ false,\r
+ 0,\r
+ TransactionController.MODE_TABLE,\r
+ TransactionController.ISOLATION_SERIALIZABLE);\r
+\r
+ indexCC.checkConsistency();\r
+ indexCC.close();\r
+ indexCC = null;\r
+\r
+ /* if index is for a constraint check that the constraint exists */\r
+\r
+ if (indexCD.isConstraint())\r
+ {\r
+ constraintDesc = dd.getConstraintDescriptor(td, indexCD.getUUID());\r
+ if (constraintDesc == null)\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.LANG_OBJECT_NOT_FOUND,\r
+ "CONSTRAINT for INDEX",\r
+ indexCD.getConglomerateName());\r
+ }\r
+ }\r
+\r
+ /*\r
+ ** Set the base row count when we get to the first index.\r
+ ** We do this here, rather than outside the index loop, so\r
+ ** we won't do the work of counting the rows in the base table\r
+ ** if there are no indexes to check.\r
+ */\r
+ if (baseRowCount < 0)\r
+ {\r
+ scan = tc.openScan(heapCD.getConglomerateNumber(),\r
+ false, // hold\r
+ 0, // not forUpdate\r
+ TransactionController.MODE_TABLE,\r
+ TransactionController.ISOLATION_SERIALIZABLE,\r
+ RowUtil.EMPTY_ROW_BITSET,\r
+ null, // startKeyValue\r
+ 0, // not used with null start posn.\r
+ null, // qualifier\r
+ null, // stopKeyValue\r
+ 0); // not used with null stop posn.\r
+\r
+ /* Also, get the row location template for index rows */\r
+ rl = scan.newRowLocationTemplate();\r
+ scanRL = scan.newRowLocationTemplate();\r
+\r
+ for (baseRowCount = 0; scan.next(); baseRowCount++)\r
+ ; /* Empty statement */\r
+\r
+ scan.close();\r
+ scan = null;\r
+ }\r
+\r
+ baseColumnPositions =\r
+ indexCD.getIndexDescriptor().baseColumnPositions();\r
+ baseColumns = baseColumnPositions.length;\r
+\r
+ FormatableBitSet indexColsBitSet = new FormatableBitSet();\r
+ for (int i = 0; i < baseColumns; i++)\r
+ {\r
+ indexColsBitSet.grow(baseColumnPositions[i]);\r
+ indexColsBitSet.set(baseColumnPositions[i] - 1);\r
+ }\r
+\r
+ /* Get one row template for the index scan, and one for the fetch */\r
+ indexRow = ef.getValueRow(baseColumns + 1);\r
+\r
+ /* Fill the row with nulls of the correct type */\r
+ for (int column = 0; column < baseColumns; column++)\r
+ {\r
+ /* Column positions in the data dictionary are one-based */\r
+ ColumnDescriptor cd = td.getColumnDescriptor(baseColumnPositions[column]);\r
+ indexRow.setColumn(column + 1,\r
+ cd.getType().getNull());\r
+ }\r
+\r
+ /* Set the row location in the last column of the index row */\r
+ indexRow.setColumn(baseColumns + 1, rl);\r
+\r
+ /* Do a full scan of the index */\r
+ scan = tc.openScan(indexCD.getConglomerateNumber(),\r
+ false, // hold\r
+ 0, // not forUpdate\r
+ TransactionController.MODE_TABLE,\r
+ TransactionController.ISOLATION_SERIALIZABLE,\r
+ (FormatableBitSet) null,\r
+ null, // startKeyValue\r
+ 0, // not used with null start posn.\r
+ null, // qualifier\r
+ null, // stopKeyValue\r
+ 0); // not used with null stop posn.\r
+\r
+ DataValueDescriptor[] baseRowIndexOrder = \r
+ new DataValueDescriptor[baseColumns];\r
+ DataValueDescriptor[] baseObjectArray = baseRow.getRowArray();\r
+\r
+ for (int i = 0; i < baseColumns; i++)\r
+ {\r
+ baseRowIndexOrder[i] = baseObjectArray[baseColumnPositions[i] - 1];\r
+ }\r
+ \r
+ /* Get the index rows and count them */\r
+ for (indexRows = 0; scan.fetchNext(indexRow.getRowArray()); indexRows++)\r
+ {\r
+ /*\r
+ ** Get the base row using the RowLocation in the index row,\r
+ ** which is in the last column. \r
+ */\r
+ RowLocation baseRL = (RowLocation) indexRow.getColumn(baseColumns + 1);\r
+\r
+ boolean base_row_exists = \r
+ baseCC.fetch(\r
+ baseRL, baseObjectArray, indexColsBitSet);\r
+\r
+ /* Throw exception if fetch() returns false */\r
+ if (! base_row_exists)\r
+ {\r
+ String indexName = indexCD.getConglomerateName();\r
+ throw StandardException.newException(SQLState.LANG_INCONSISTENT_ROW_LOCATION, \r
+ (schemaName + "." + tableName),\r
+ indexName, \r
+ baseRL.toString(),\r
+ indexRow.toString());\r
+ }\r
+\r
+ /* Compare all the column values */\r
+ for (int column = 0; column < baseColumns; column++)\r
+ {\r
+ DataValueDescriptor indexColumn =\r
+ indexRow.getColumn(column + 1);\r
+ DataValueDescriptor baseColumn =\r
+ baseRowIndexOrder[column];\r
+\r
+ /*\r
+ ** With this form of compare(), null is considered equal\r
+ ** to null.\r
+ */\r
+ if (indexColumn.compare(baseColumn) != 0)\r
+ {\r
+ ColumnDescriptor cd = \r
+ td.getColumnDescriptor(\r
+ baseColumnPositions[column]);\r
+\r
+ /*\r
+ System.out.println(\r
+ "SQLState.LANG_INDEX_COLUMN_NOT_EQUAL:" +\r
+ "indexCD.getConglomerateName()" + indexCD.getConglomerateName() +\r
+ ";td.getSchemaName() = " + td.getSchemaName() +\r
+ ";td.getName() = " + td.getName() +\r
+ ";baseRL.toString() = " + baseRL.toString() +\r
+ ";cd.getColumnName() = " + cd.getColumnName() +\r
+ ";indexColumn.toString() = " + indexColumn.toString() +\r
+ ";baseColumn.toString() = " + baseColumn.toString() +\r
+ ";indexRow.toString() = " + indexRow.toString());\r
+ */\r
+\r
+ throw StandardException.newException(\r
+ SQLState.LANG_INDEX_COLUMN_NOT_EQUAL, \r
+ indexCD.getConglomerateName(),\r
+ td.getSchemaName(),\r
+ td.getName(),\r
+ baseRL.toString(),\r
+ cd.getColumnName(),\r
+ indexColumn.toString(),\r
+ baseColumn.toString(),\r
+ indexRow.toString());\r
+ }\r
+ }\r
+ }\r
+\r
+ /* Clean up after the index scan */\r
+ scan.close();\r
+ scan = null;\r
+\r
+ /*\r
+ ** The index is supposed to have the same number of rows as the\r
+ ** base conglomerate.\r
+ */\r
+ if (indexRows != baseRowCount)\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_INDEX_ROW_COUNT_MISMATCH, \r
+ indexCD.getConglomerateName(),\r
+ td.getSchemaName(),\r
+ td.getName(),\r
+ Long.toString(indexRows),\r
+ Long.toString(baseRowCount));\r
+ }\r
+ }\r
+ /* check that all constraints have backing index */\r
+ ConstraintDescriptorList constraintDescList = \r
+ dd.getConstraintDescriptors(td);\r
+ for (int index = 0; index < constraintDescList.size(); index++)\r
+ {\r
+ constraintDesc = constraintDescList.elementAt(index);\r
+ if (constraintDesc.hasBackingIndex())\r
+ {\r
+ ConglomerateDescriptor conglomDesc;\r
+\r
+ conglomDesc = td.getConglomerateDescriptor(\r
+ constraintDesc.getConglomerateId());\r
+ if (conglomDesc == null)\r
+ {\r
+ throw StandardException.newException(\r
+ SQLState.LANG_OBJECT_NOT_FOUND,\r
+ "INDEX for CONSTRAINT",\r
+ constraintDesc.getConstraintName());\r
+ }\r
+ }\r
+ }\r
+ \r
+ }\r
+ catch (StandardException se)\r
+ {\r
+ throw PublicAPI.wrapStandardException(se);\r
+ }\r
+ finally\r
+ {\r
+ try\r
+ {\r
+ /* Clean up before we leave */\r
+ if (baseCC != null)\r
+ {\r
+ baseCC.close();\r
+ baseCC = null;\r
+ }\r
+ if (indexCC != null)\r
+ {\r
+ indexCC.close();\r
+ indexCC = null;\r
+ }\r
+ if (scan != null)\r
+ {\r
+ scan.close();\r
+ scan = null;\r
+ }\r
+ }\r
+ catch (StandardException se)\r
+ {\r
+ throw PublicAPI.wrapStandardException(se);\r
+ }\r
+ }\r
+\r
+ return true;\r
+ }\r
+}\r