1 | /* |
2 | |
3 | Derby - Class org.apache.derby.iapi.db.ConsistencyChecker |
4 | |
5 | Copyright 1999, 2004 The Apache Software Foundation or its licensors, as applicable. |
6 | |
7 | Licensed under the Apache License, Version 2.0 (the "License"); |
8 | you may not use this file except in compliance with the License. |
9 | You may obtain a copy of the License at |
10 | |
11 | http://www.apache.org/licenses/LICENSE-2.0 |
12 | |
13 | Unless required by applicable law or agreed to in writing, software |
14 | distributed under the License is distributed on an "AS IS" BASIS, |
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 | See the License for the specific language governing permissions and |
17 | limitations under the License. |
18 | |
19 | */ |
20 | |
21 | package org.apache.derby.iapi.db; |
22 | |
23 | import org.apache.derby.iapi.error.StandardException; |
24 | import org.apache.derby.iapi.error.PublicAPI; |
25 | |
26 | import org.apache.derby.iapi.sql.dictionary.DataDictionaryContext; |
27 | import org.apache.derby.iapi.sql.dictionary.DataDictionary; |
28 | import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor; |
29 | import org.apache.derby.iapi.sql.dictionary.TableDescriptor; |
30 | import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor; |
31 | import org.apache.derby.iapi.sql.dictionary.ColumnDescriptorList; |
32 | import org.apache.derby.iapi.sql.dictionary.ConstraintDescriptor; |
33 | import org.apache.derby.iapi.sql.dictionary.ConstraintDescriptorList; |
34 | import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor; |
35 | |
36 | import org.apache.derby.iapi.sql.depend.DependencyManager; |
37 | |
38 | import org.apache.derby.iapi.sql.execute.ExecRow; |
39 | import org.apache.derby.iapi.sql.execute.ExecutionContext; |
40 | |
41 | import org.apache.derby.iapi.types.DataValueDescriptor; |
42 | import org.apache.derby.iapi.types.DataValueFactory; |
43 | |
44 | |
45 | import org.apache.derby.iapi.sql.conn.LanguageConnectionContext; |
46 | import org.apache.derby.iapi.sql.conn.ConnectionUtil; |
47 | |
48 | import org.apache.derby.iapi.store.access.TransactionController; |
49 | import org.apache.derby.iapi.types.RowLocation; |
50 | import org.apache.derby.iapi.store.access.ScanController; |
51 | import org.apache.derby.iapi.store.access.ConglomerateController; |
52 | import org.apache.derby.iapi.store.access.RowUtil; |
53 | |
54 | import org.apache.derby.iapi.services.sanity.SanityManager; |
55 | |
56 | import org.apache.derby.iapi.reference.SQLState; |
57 | |
58 | import org.apache.derby.iapi.services.io.FormatableBitSet; |
59 | |
60 | import java.sql.SQLException; |
61 | |
62 | /** |
63 | * The ConsistencyChecker class provides static methods for verifying |
64 | * the consistency of the data stored within a database. |
65 | * |
66 | * |
67 | <p>This class can only be used within an SQL-J statement, a Java procedure or a server side Java method. |
68 | <p>This class can be accessed using the class alias <code> CONSISTENCYCHECKER </code> in SQL-J statements. |
69 | */ |
70 | public class ConsistencyChecker |
71 | { |
72 | |
73 | /** no requirement for a constructor */ |
74 | private ConsistencyChecker() { |
75 | } |
76 | |
77 | /** |
78 | * Check the named table, ensuring that all of its indexes are consistent |
79 | * with the base table. |
80 | * Use this |
81 | * method only within an SQL-J statement; do not call it directly. |
82 | * <P>When tables are consistent, the method returns true. Otherwise, the method throws an exception. |
83 | * <p>To check the consistency of a single table: |
84 | * <p><code> |
85 | * VALUES ConsistencyChecker::checkTable(<i>SchemaName</i>, <i>TableName</i>)</code></p> |
86 | * <P>For example, to check the consistency of the table <i>APP.Flights</i>: |
87 | * <p><code> |
88 | * VALUES ConsistencyChecker::checkTable('APP', 'FLIGHTS')</code></p> |
89 | * <p>To check the consistency of all of the tables in the 'APP' schema, |
90 | * stopping at the first failure: |
91 | * |
92 | * <P><code>SELECT tablename, ConsistencyChecker::checkTable(<br> |
93 | * 'APP', tablename)<br> |
94 | * FROM sys.sysschemas s, sys.systables t |
95 | * WHERE s.schemaname = 'APP' AND s.schemaid = t.schemaid</code> |
96 | * |
97 | * <p> To check the consistency of an entire database, stopping at the first failure: |
98 | * |
99 | * <p><code>SELECT schemaname, tablename,<br> |
100 | * ConsistencyChecker::checkTable(schemaname, tablename)<br> |
101 | * FROM sys.sysschemas s, sys.systables t<br> |
102 | * WHERE s.schemaid = t.schemaid</code> |
103 | * |
104 | * |
105 | * |
106 | * @param schemaName The schema name of the table. |
107 | * @param tableName The name of the table |
108 | * |
109 | * @return true, if the table is consistent, exception thrown if inconsistent |
110 | * |
111 | * @exception SQLException Thrown if some inconsistency |
112 | * is found, or if some unexpected |
113 | * exception is thrown.. |
114 | */ |
115 | public static boolean checkTable(String schemaName, String tableName) |
116 | throws SQLException |
117 | { |
118 | DataDictionary dd; |
119 | TableDescriptor td; |
120 | long baseRowCount = -1; |
121 | TransactionController tc; |
122 | ConglomerateDescriptor heapCD; |
123 | ConglomerateDescriptor indexCD; |
124 | ExecRow baseRow; |
125 | ExecRow indexRow; |
126 | RowLocation rl = null; |
127 | RowLocation scanRL = null; |
128 | ScanController scan = null; |
129 | int[] baseColumnPositions; |
130 | int baseColumns = 0; |
131 | DataValueFactory dvf; |
132 | long indexRows; |
133 | ConglomerateController baseCC = null; |
134 | ConglomerateController indexCC = null; |
135 | ExecutionContext ec; |
136 | SchemaDescriptor sd; |
137 | ConstraintDescriptor constraintDesc; |
138 | |
139 | LanguageConnectionContext lcc = ConnectionUtil.getCurrentLCC(); |
140 | tc = lcc.getTransactionExecute(); |
141 | |
142 | try { |
143 | |
144 | dd = lcc.getDataDictionary(); |
145 | |
146 | dvf = lcc.getDataValueFactory(); |
147 | |
148 | ec = lcc.getExecutionContext() ; |
149 | |
150 | sd = dd.getSchemaDescriptor(schemaName, tc, true); |
151 | td = dd.getTableDescriptor(tableName, sd); |
152 | |
153 | if (td == null) |
154 | { |
155 | throw StandardException.newException( |
156 | SQLState.LANG_TABLE_NOT_FOUND, |
157 | schemaName + "." + tableName); |
158 | } |
159 | |
160 | /* Skip views */ |
161 | if (td.getTableType() == TableDescriptor.VIEW_TYPE) |
162 | { |
163 | return true; |
164 | } |
165 | |
166 | /* Open the heap for reading */ |
167 | baseCC = tc.openConglomerate( |
168 | td.getHeapConglomerateId(), false, 0, |
169 | TransactionController.MODE_TABLE, |
170 | TransactionController.ISOLATION_SERIALIZABLE); |
171 | |
172 | /* Check the consistency of the heap */ |
173 | baseCC.checkConsistency(); |
174 | |
175 | heapCD = td.getConglomerateDescriptor(td.getHeapConglomerateId()); |
176 | |
177 | /* Get a row template for the base table */ |
178 | baseRow = ec.getExecutionFactory().getValueRow(td.getNumberOfColumns()); |
179 | |
180 | /* Fill the row with nulls of the correct type */ |
181 | ColumnDescriptorList cdl = td.getColumnDescriptorList(); |
182 | int cdlSize = cdl.size(); |
183 | |
184 | for (int index = 0; index < cdlSize; index++) |
185 | { |
186 | ColumnDescriptor cd = (ColumnDescriptor) cdl.elementAt(index); |
187 | baseRow.setColumn(cd.getPosition(), |
188 | cd.getType().getNull()); |
189 | } |
190 | |
191 | /* Look at all the indexes on the table */ |
192 | ConglomerateDescriptor[] cds = td.getConglomerateDescriptors(); |
193 | for (int index = 0; index < cds.length; index++) |
194 | { |
195 | indexCD = cds[index]; |
196 | /* Skip the heap */ |
197 | if ( ! indexCD.isIndex()) |
198 | continue; |
199 | |
200 | /* Check the internal consistency of the index */ |
201 | indexCC = |
202 | tc.openConglomerate( |
203 | indexCD.getConglomerateNumber(), |
204 | false, |
205 | 0, |
206 | TransactionController.MODE_TABLE, |
207 | TransactionController.ISOLATION_SERIALIZABLE); |
208 | |
209 | indexCC.checkConsistency(); |
210 | indexCC.close(); |
211 | indexCC = null; |
212 | |
213 | /* if index is for a constraint check that the constraint exists */ |
214 | |
215 | if (indexCD.isConstraint()) |
216 | { |
217 | constraintDesc = dd.getConstraintDescriptor(td, indexCD.getUUID()); |
218 | if (constraintDesc == null) |
219 | { |
220 | throw StandardException.newException( |
221 | SQLState.LANG_OBJECT_NOT_FOUND, |
222 | "CONSTRAINT for INDEX", |
223 | indexCD.getConglomerateName()); |
224 | } |
225 | } |
226 | |
227 | /* |
228 | ** Set the base row count when we get to the first index. |
229 | ** We do this here, rather than outside the index loop, so |
230 | ** we won't do the work of counting the rows in the base table |
231 | ** if there are no indexes to check. |
232 | */ |
233 | if (baseRowCount < 0) |
234 | { |
235 | scan = tc.openScan(heapCD.getConglomerateNumber(), |
236 | false, // hold |
237 | 0, // not forUpdate |
238 | TransactionController.MODE_TABLE, |
239 | TransactionController.ISOLATION_SERIALIZABLE, |
240 | RowUtil.EMPTY_ROW_BITSET, |
241 | null, // startKeyValue |
242 | 0, // not used with null start posn. |
243 | null, // qualifier |
244 | null, // stopKeyValue |
245 | 0); // not used with null stop posn. |
246 | |
247 | /* Also, get the row location template for index rows */ |
248 | rl = scan.newRowLocationTemplate(); |
249 | scanRL = scan.newRowLocationTemplate(); |
250 | |
251 | for (baseRowCount = 0; scan.next(); baseRowCount++) |
252 | ; /* Empty statement */ |
253 | |
254 | scan.close(); |
255 | scan = null; |
256 | } |
257 | |
258 | baseColumnPositions = |
259 | indexCD.getIndexDescriptor().baseColumnPositions(); |
260 | baseColumns = baseColumnPositions.length; |
261 | |
262 | FormatableBitSet indexColsBitSet = new FormatableBitSet(); |
263 | for (int i = 0; i < baseColumns; i++) |
264 | { |
265 | indexColsBitSet.grow(baseColumnPositions[i]); |
266 | indexColsBitSet.set(baseColumnPositions[i] - 1); |
267 | } |
268 | |
269 | /* Get one row template for the index scan, and one for the fetch */ |
270 | indexRow = ec.getExecutionFactory().getValueRow(baseColumns + 1); |
271 | |
272 | /* Fill the row with nulls of the correct type */ |
273 | for (int column = 0; column < baseColumns; column++) |
274 | { |
275 | /* Column positions in the data dictionary are one-based */ |
276 | ColumnDescriptor cd = td.getColumnDescriptor(baseColumnPositions[column]); |
277 | indexRow.setColumn(column + 1, |
278 | cd.getType().getNull()); |
279 | } |
280 | |
281 | /* Set the row location in the last column of the index row */ |
282 | indexRow.setColumn(baseColumns + 1, rl); |
283 | |
284 | /* Do a full scan of the index */ |
285 | scan = tc.openScan(indexCD.getConglomerateNumber(), |
286 | false, // hold |
287 | 0, // not forUpdate |
288 | TransactionController.MODE_TABLE, |
289 | TransactionController.ISOLATION_SERIALIZABLE, |
290 | (FormatableBitSet) null, |
291 | null, // startKeyValue |
292 | 0, // not used with null start posn. |
293 | null, // qualifier |
294 | null, // stopKeyValue |
295 | 0); // not used with null stop posn. |
296 | |
297 | DataValueDescriptor[] baseRowIndexOrder = |
298 | new DataValueDescriptor[baseColumns]; |
299 | DataValueDescriptor[] baseObjectArray = baseRow.getRowArray(); |
300 | |
301 | for (int i = 0; i < baseColumns; i++) |
302 | { |
303 | baseRowIndexOrder[i] = baseObjectArray[baseColumnPositions[i] - 1]; |
304 | } |
305 | |
306 | /* Get the index rows and count them */ |
307 | for (indexRows = 0; scan.fetchNext(indexRow.getRowArray()); indexRows++) |
308 | { |
309 | /* |
310 | ** Get the base row using the RowLocation in the index row, |
311 | ** which is in the last column. |
312 | */ |
313 | RowLocation baseRL = (RowLocation) indexRow.getColumn(baseColumns + 1); |
314 | |
315 | boolean base_row_exists = |
316 | baseCC.fetch( |
317 | baseRL, baseObjectArray, indexColsBitSet); |
318 | |
319 | /* Throw exception if fetch() returns false */ |
320 | if (! base_row_exists) |
321 | { |
322 | String indexName = indexCD.getConglomerateName(); |
323 | throw StandardException.newException(SQLState.LANG_INCONSISTENT_ROW_LOCATION, |
324 | (schemaName + "." + tableName), |
325 | indexName, |
326 | baseRL.toString(), |
327 | indexRow.toString()); |
328 | } |
329 | |
330 | /* Compare all the column values */ |
331 | for (int column = 0; column < baseColumns; column++) |
332 | { |
333 | DataValueDescriptor indexColumn = |
334 | indexRow.getColumn(column + 1); |
335 | DataValueDescriptor baseColumn = |
336 | baseRowIndexOrder[column]; |
337 | |
338 | /* |
339 | ** With this form of compare(), null is considered equal |
340 | ** to null. |
341 | */ |
342 | if (indexColumn.compare(baseColumn) != 0) |
343 | { |
344 | ColumnDescriptor cd = |
345 | td.getColumnDescriptor( |
346 | baseColumnPositions[column]); |
347 | |
348 | /* |
349 | System.out.println( |
350 | "SQLState.LANG_INDEX_COLUMN_NOT_EQUAL:" + |
351 | "indexCD.getConglomerateName()" + indexCD.getConglomerateName() + |
352 | ";td.getSchemaName() = " + td.getSchemaName() + |
353 | ";td.getName() = " + td.getName() + |
354 | ";baseRL.toString() = " + baseRL.toString() + |
355 | ";cd.getColumnName() = " + cd.getColumnName() + |
356 | ";indexColumn.toString() = " + indexColumn.toString() + |
357 | ";baseColumn.toString() = " + baseColumn.toString() + |
358 | ";indexRow.toString() = " + indexRow.toString()); |
359 | */ |
360 | |
361 | throw StandardException.newException( |
362 | SQLState.LANG_INDEX_COLUMN_NOT_EQUAL, |
363 | indexCD.getConglomerateName(), |
364 | td.getSchemaName(), |
365 | td.getName(), |
366 | baseRL.toString(), |
367 | cd.getColumnName(), |
368 | indexColumn.toString(), |
369 | baseColumn.toString(), |
370 | indexRow.toString()); |
371 | } |
372 | } |
373 | } |
374 | |
375 | /* Clean up after the index scan */ |
376 | scan.close(); |
377 | scan = null; |
378 | |
379 | /* |
380 | ** The index is supposed to have the same number of rows as the |
381 | ** base conglomerate. |
382 | */ |
383 | if (indexRows != baseRowCount) |
384 | { |
385 | throw StandardException.newException(SQLState.LANG_INDEX_ROW_COUNT_MISMATCH, |
386 | indexCD.getConglomerateName(), |
387 | td.getSchemaName(), |
388 | td.getName(), |
389 | Long.toString(indexRows), |
390 | Long.toString(baseRowCount)); |
391 | } |
392 | } |
393 | /* check that all constraints have backing index */ |
394 | ConstraintDescriptorList constraintDescList = |
395 | dd.getConstraintDescriptors(td); |
396 | for (int index = 0; index < constraintDescList.size(); index++) |
397 | { |
398 | constraintDesc = constraintDescList.elementAt(index); |
399 | if (constraintDesc.hasBackingIndex()) |
400 | { |
401 | ConglomerateDescriptor conglomDesc; |
402 | |
403 | conglomDesc = td.getConglomerateDescriptor( |
404 | constraintDesc.getConglomerateId()); |
405 | if (conglomDesc == null) |
406 | { |
407 | throw StandardException.newException( |
408 | SQLState.LANG_OBJECT_NOT_FOUND, |
409 | "INDEX for CONSTRAINT", |
410 | constraintDesc.getConstraintName()); |
411 | } |
412 | } |
413 | } |
414 | |
415 | } |
416 | catch (StandardException se) |
417 | { |
418 | throw PublicAPI.wrapStandardException(se); |
419 | } |
420 | finally |
421 | { |
422 | try |
423 | { |
424 | /* Clean up before we leave */ |
425 | if (baseCC != null) |
426 | { |
427 | baseCC.close(); |
428 | baseCC = null; |
429 | } |
430 | if (indexCC != null) |
431 | { |
432 | indexCC.close(); |
433 | indexCC = null; |
434 | } |
435 | if (scan != null) |
436 | { |
437 | scan.close(); |
438 | scan = null; |
439 | } |
440 | } |
441 | catch (StandardException se) |
442 | { |
443 | throw PublicAPI.wrapStandardException(se); |
444 | } |
445 | } |
446 | |
447 | return true; |
448 | } |
449 | } |