001: /*
002:
003: Derby - Class org.apache.derby.iapi.db.ConsistencyChecker
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.iapi.db;
023:
024: import org.apache.derby.iapi.error.StandardException;
025: import org.apache.derby.iapi.error.PublicAPI;
026:
027: import org.apache.derby.iapi.sql.dictionary.DataDictionaryContext;
028: import org.apache.derby.iapi.sql.dictionary.DataDictionary;
029: import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor;
030: import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
031: import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor;
032: import org.apache.derby.iapi.sql.dictionary.ColumnDescriptorList;
033: import org.apache.derby.iapi.sql.dictionary.ConstraintDescriptor;
034: import org.apache.derby.iapi.sql.dictionary.ConstraintDescriptorList;
035: import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor;
036:
037: import org.apache.derby.iapi.sql.depend.DependencyManager;
038:
039: import org.apache.derby.iapi.sql.execute.ExecRow;
040: import org.apache.derby.iapi.sql.execute.ExecutionContext;
041:
042: import org.apache.derby.iapi.types.DataValueDescriptor;
043: import org.apache.derby.iapi.types.DataValueFactory;
044:
045: import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
046: import org.apache.derby.iapi.sql.conn.ConnectionUtil;
047:
048: import org.apache.derby.iapi.store.access.TransactionController;
049: import org.apache.derby.iapi.types.RowLocation;
050: import org.apache.derby.iapi.store.access.ScanController;
051: import org.apache.derby.iapi.store.access.ConglomerateController;
052: import org.apache.derby.iapi.store.access.RowUtil;
053:
054: import org.apache.derby.iapi.services.sanity.SanityManager;
055:
056: import org.apache.derby.iapi.reference.SQLState;
057:
058: import org.apache.derby.iapi.services.io.FormatableBitSet;
059:
060: import java.sql.SQLException;
061:
062: /**
063: * The ConsistencyChecker class provides static methods for verifying
064: * the consistency of the data stored within a database.
065: *
066: *
067: <p>This class can only be used within an SQL-J statement, a Java procedure or a server side Java method.
068: <p>This class can be accessed using the class alias <code> CONSISTENCYCHECKER </code> in SQL-J statements.
069: */
070: public class ConsistencyChecker {
071:
072: /** no requirement for a constructor */
073: private ConsistencyChecker() {
074: }
075:
076: /**
077: * Check the named table, ensuring that all of its indexes are consistent
078: * with the base table.
079: * Use this
080: * method only within an SQL-J statement; do not call it directly.
081: * <P>When tables are consistent, the method returns true. Otherwise, the method throws an exception.
082: * <p>To check the consistency of a single table:
083: * <p><code>
084: * VALUES ConsistencyChecker::checkTable(<i>SchemaName</i>, <i>TableName</i>)</code></p>
085: * <P>For example, to check the consistency of the table <i>APP.Flights</i>:
086: * <p><code>
087: * VALUES ConsistencyChecker::checkTable('APP', 'FLIGHTS')</code></p>
088: * <p>To check the consistency of all of the tables in the 'APP' schema,
089: * stopping at the first failure:
090: *
091: * <P><code>SELECT tablename, ConsistencyChecker::checkTable(<br>
092: * 'APP', tablename)<br>
093: * FROM sys.sysschemas s, sys.systables t
094: * WHERE s.schemaname = 'APP' AND s.schemaid = t.schemaid</code>
095: *
096: * <p> To check the consistency of an entire database, stopping at the first failure:
097: *
098: * <p><code>SELECT schemaname, tablename,<br>
099: * ConsistencyChecker::checkTable(schemaname, tablename)<br>
100: * FROM sys.sysschemas s, sys.systables t<br>
101: * WHERE s.schemaid = t.schemaid</code>
102: *
103: *
104: *
105: * @param schemaName The schema name of the table.
106: * @param tableName The name of the table
107: *
108: * @return true, if the table is consistent, exception thrown if inconsistent
109: *
110: * @exception SQLException Thrown if some inconsistency
111: * is found, or if some unexpected
112: * exception is thrown..
113: */
114: public static boolean checkTable(String schemaName, String tableName)
115: throws SQLException {
116: DataDictionary dd;
117: TableDescriptor td;
118: long baseRowCount = -1;
119: TransactionController tc;
120: ConglomerateDescriptor heapCD;
121: ConglomerateDescriptor indexCD;
122: ExecRow baseRow;
123: ExecRow indexRow;
124: RowLocation rl = null;
125: RowLocation scanRL = null;
126: ScanController scan = null;
127: int[] baseColumnPositions;
128: int baseColumns = 0;
129: DataValueFactory dvf;
130: long indexRows;
131: ConglomerateController baseCC = null;
132: ConglomerateController indexCC = null;
133: ExecutionContext ec;
134: SchemaDescriptor sd;
135: ConstraintDescriptor constraintDesc;
136:
137: LanguageConnectionContext lcc = ConnectionUtil.getCurrentLCC();
138: tc = lcc.getTransactionExecute();
139:
140: try {
141:
142: dd = lcc.getDataDictionary();
143:
144: dvf = lcc.getDataValueFactory();
145:
146: ec = lcc.getExecutionContext();
147:
148: sd = dd.getSchemaDescriptor(schemaName, tc, true);
149: td = dd.getTableDescriptor(tableName, sd);
150:
151: if (td == null) {
152: throw StandardException.newException(
153: SQLState.LANG_TABLE_NOT_FOUND, schemaName + "."
154: + tableName);
155: }
156:
157: /* Skip views */
158: if (td.getTableType() == TableDescriptor.VIEW_TYPE) {
159: return true;
160: }
161:
162: /* Open the heap for reading */
163: baseCC = tc.openConglomerate(td.getHeapConglomerateId(),
164: false, 0, TransactionController.MODE_TABLE,
165: TransactionController.ISOLATION_SERIALIZABLE);
166:
167: /* Check the consistency of the heap */
168: baseCC.checkConsistency();
169:
170: heapCD = td.getConglomerateDescriptor(td
171: .getHeapConglomerateId());
172:
173: /* Get a row template for the base table */
174: baseRow = ec.getExecutionFactory().getValueRow(
175: td.getNumberOfColumns());
176:
177: /* Fill the row with nulls of the correct type */
178: ColumnDescriptorList cdl = td.getColumnDescriptorList();
179: int cdlSize = cdl.size();
180:
181: for (int index = 0; index < cdlSize; index++) {
182: ColumnDescriptor cd = (ColumnDescriptor) cdl
183: .elementAt(index);
184: baseRow.setColumn(cd.getPosition(), cd.getType()
185: .getNull());
186: }
187:
188: /* Look at all the indexes on the table */
189: ConglomerateDescriptor[] cds = td
190: .getConglomerateDescriptors();
191: for (int index = 0; index < cds.length; index++) {
192: indexCD = cds[index];
193: /* Skip the heap */
194: if (!indexCD.isIndex())
195: continue;
196:
197: /* Check the internal consistency of the index */
198: indexCC = tc.openConglomerate(indexCD
199: .getConglomerateNumber(), false, 0,
200: TransactionController.MODE_TABLE,
201: TransactionController.ISOLATION_SERIALIZABLE);
202:
203: indexCC.checkConsistency();
204: indexCC.close();
205: indexCC = null;
206:
207: /* if index is for a constraint check that the constraint exists */
208:
209: if (indexCD.isConstraint()) {
210: constraintDesc = dd.getConstraintDescriptor(td,
211: indexCD.getUUID());
212: if (constraintDesc == null) {
213: throw StandardException.newException(
214: SQLState.LANG_OBJECT_NOT_FOUND,
215: "CONSTRAINT for INDEX", indexCD
216: .getConglomerateName());
217: }
218: }
219:
220: /*
221: ** Set the base row count when we get to the first index.
222: ** We do this here, rather than outside the index loop, so
223: ** we won't do the work of counting the rows in the base table
224: ** if there are no indexes to check.
225: */
226: if (baseRowCount < 0) {
227: scan = tc
228: .openScan(
229: heapCD.getConglomerateNumber(),
230: false, // hold
231: 0, // not forUpdate
232: TransactionController.MODE_TABLE,
233: TransactionController.ISOLATION_SERIALIZABLE,
234: RowUtil.EMPTY_ROW_BITSET, null, // startKeyValue
235: 0, // not used with null start posn.
236: null, // qualifier
237: null, // stopKeyValue
238: 0); // not used with null stop posn.
239:
240: /* Also, get the row location template for index rows */
241: rl = scan.newRowLocationTemplate();
242: scanRL = scan.newRowLocationTemplate();
243:
244: for (baseRowCount = 0; scan.next(); baseRowCount++)
245: ; /* Empty statement */
246:
247: scan.close();
248: scan = null;
249: }
250:
251: baseColumnPositions = indexCD.getIndexDescriptor()
252: .baseColumnPositions();
253: baseColumns = baseColumnPositions.length;
254:
255: FormatableBitSet indexColsBitSet = new FormatableBitSet();
256: for (int i = 0; i < baseColumns; i++) {
257: indexColsBitSet.grow(baseColumnPositions[i]);
258: indexColsBitSet.set(baseColumnPositions[i] - 1);
259: }
260:
261: /* Get one row template for the index scan, and one for the fetch */
262: indexRow = ec.getExecutionFactory().getValueRow(
263: baseColumns + 1);
264:
265: /* Fill the row with nulls of the correct type */
266: for (int column = 0; column < baseColumns; column++) {
267: /* Column positions in the data dictionary are one-based */
268: ColumnDescriptor cd = td
269: .getColumnDescriptor(baseColumnPositions[column]);
270: indexRow.setColumn(column + 1, cd.getType()
271: .getNull());
272: }
273:
274: /* Set the row location in the last column of the index row */
275: indexRow.setColumn(baseColumns + 1, rl);
276:
277: /* Do a full scan of the index */
278: scan = tc.openScan(
279: indexCD.getConglomerateNumber(),
280: false, // hold
281: 0, // not forUpdate
282: TransactionController.MODE_TABLE,
283: TransactionController.ISOLATION_SERIALIZABLE,
284: (FormatableBitSet) null, null, // startKeyValue
285: 0, // not used with null start posn.
286: null, // qualifier
287: null, // stopKeyValue
288: 0); // not used with null stop posn.
289:
290: DataValueDescriptor[] baseRowIndexOrder = new DataValueDescriptor[baseColumns];
291: DataValueDescriptor[] baseObjectArray = baseRow
292: .getRowArray();
293:
294: for (int i = 0; i < baseColumns; i++) {
295: baseRowIndexOrder[i] = baseObjectArray[baseColumnPositions[i] - 1];
296: }
297:
298: /* Get the index rows and count them */
299: for (indexRows = 0; scan.fetchNext(indexRow
300: .getRowArray()); indexRows++) {
301: /*
302: ** Get the base row using the RowLocation in the index row,
303: ** which is in the last column.
304: */
305: RowLocation baseRL = (RowLocation) indexRow
306: .getColumn(baseColumns + 1);
307:
308: boolean base_row_exists = baseCC.fetch(baseRL,
309: baseObjectArray, indexColsBitSet);
310:
311: /* Throw exception if fetch() returns false */
312: if (!base_row_exists) {
313: String indexName = indexCD
314: .getConglomerateName();
315: throw StandardException
316: .newException(
317: SQLState.LANG_INCONSISTENT_ROW_LOCATION,
318: (schemaName + "." + tableName),
319: indexName, baseRL.toString(),
320: indexRow.toString());
321: }
322:
323: /* Compare all the column values */
324: for (int column = 0; column < baseColumns; column++) {
325: DataValueDescriptor indexColumn = indexRow
326: .getColumn(column + 1);
327: DataValueDescriptor baseColumn = baseRowIndexOrder[column];
328:
329: /*
330: ** With this form of compare(), null is considered equal
331: ** to null.
332: */
333: if (indexColumn.compare(baseColumn) != 0) {
334: ColumnDescriptor cd = td
335: .getColumnDescriptor(baseColumnPositions[column]);
336:
337: /*
338: System.out.println(
339: "SQLState.LANG_INDEX_COLUMN_NOT_EQUAL:" +
340: "indexCD.getConglomerateName()" + indexCD.getConglomerateName() +
341: ";td.getSchemaName() = " + td.getSchemaName() +
342: ";td.getName() = " + td.getName() +
343: ";baseRL.toString() = " + baseRL.toString() +
344: ";cd.getColumnName() = " + cd.getColumnName() +
345: ";indexColumn.toString() = " + indexColumn.toString() +
346: ";baseColumn.toString() = " + baseColumn.toString() +
347: ";indexRow.toString() = " + indexRow.toString());
348: */
349:
350: throw StandardException
351: .newException(
352: SQLState.LANG_INDEX_COLUMN_NOT_EQUAL,
353: indexCD
354: .getConglomerateName(),
355: td.getSchemaName(), td
356: .getName(), baseRL
357: .toString(), cd
358: .getColumnName(),
359: indexColumn.toString(),
360: baseColumn.toString(),
361: indexRow.toString());
362: }
363: }
364: }
365:
366: /* Clean up after the index scan */
367: scan.close();
368: scan = null;
369:
370: /*
371: ** The index is supposed to have the same number of rows as the
372: ** base conglomerate.
373: */
374: if (indexRows != baseRowCount) {
375: throw StandardException.newException(
376: SQLState.LANG_INDEX_ROW_COUNT_MISMATCH,
377: indexCD.getConglomerateName(), td
378: .getSchemaName(), td.getName(),
379: Long.toString(indexRows), Long
380: .toString(baseRowCount));
381: }
382: }
383: /* check that all constraints have backing index */
384: ConstraintDescriptorList constraintDescList = dd
385: .getConstraintDescriptors(td);
386: for (int index = 0; index < constraintDescList.size(); index++) {
387: constraintDesc = constraintDescList.elementAt(index);
388: if (constraintDesc.hasBackingIndex()) {
389: ConglomerateDescriptor conglomDesc;
390:
391: conglomDesc = td
392: .getConglomerateDescriptor(constraintDesc
393: .getConglomerateId());
394: if (conglomDesc == null) {
395: throw StandardException.newException(
396: SQLState.LANG_OBJECT_NOT_FOUND,
397: "INDEX for CONSTRAINT", constraintDesc
398: .getConstraintName());
399: }
400: }
401: }
402:
403: } catch (StandardException se) {
404: throw PublicAPI.wrapStandardException(se);
405: } finally {
406: try {
407: /* Clean up before we leave */
408: if (baseCC != null) {
409: baseCC.close();
410: baseCC = null;
411: }
412: if (indexCC != null) {
413: indexCC.close();
414: indexCC = null;
415: }
416: if (scan != null) {
417: scan.close();
418: scan = null;
419: }
420: } catch (StandardException se) {
421: throw PublicAPI.wrapStandardException(se);
422: }
423: }
424:
425: return true;
426: }
427: }
|