typo
[csql.git] / src / tools / cacheverify.cxx
blobfeb6b0669c8eb58dc79a0249982b1646b9d9f79e
1 /***************************************************************************
2 * Copyright (C) 2007 by www.databasecache.com *
3 * Contact: praba_tuty@databasecache.com *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 ***************************************************************************/
16 #include <CSql.h>
17 #include <list>
18 #include <SqlFactory.h>
19 #include <SqlOdbcConnection.h>
20 #include <SqlOdbcStatement.h>
22 void printUsage()
24 printf("Usage: cacheverify [-U username] [-P passwd] -t tablename [-p] [-f]\n");
25 printf(" username -> username to connect with csql.\n");
26 printf(" passwd -> password for the above username to connect with csql.\n");
27 printf(" tablename -> cached table name in csql from target db.\n");
28 printf(" p -> verification at primary key field level\n");
29 printf(" f -> verification at record level\n");
30 printf(" ? -> help\n");
31 return;
34 typedef struct PrimaryKeyField {
35 int val;
36 bool inCsql;
37 bool inTrgtDb;
38 } PrimKeyFldVal;
41 typedef struct FldVal {
42 void *value;
43 DataType type;
44 int length;
45 }FldVal;
47 typedef struct Rec {
48 int val;
49 List fldValList;
50 }Record;
52 using namespace std;
54 // functions to sort the list for STL list
55 bool cmp(const PrimKeyFldVal *a, const PrimKeyFldVal *b)
57 return a->val < b->val;
60 bool cmpRec(const Record *a, const Record *b)
62 return a->val < b->val;
65 DbRetVal verifyCount(const char *tblName, long numTuples)
67 char statement[200];
68 AbsSqlConnection *con = SqlFactory::createConnection(CSqlAdapter);
69 DbRetVal rv = con->connect("root", "manager");
70 if (rv != OK) { delete con; return ErrSysInit; }
71 AbsSqlStatement *stmt = SqlFactory::createStatement(CSqlAdapter);
72 stmt->setConnection(con);
73 int count = 0;
74 int rows = 0;
75 sprintf(statement, "select count(*) from %s;", tblName);
76 rv = con->beginTrans();
77 rv = stmt->prepare(statement);
78 if(rv != OK) {
79 delete stmt; delete con;
80 printf("Prepare failed\n");
81 return rv;
83 stmt->bindField(1, &count);
84 rv = stmt->execute(rows);
85 if(rv != OK) {
86 delete stmt; delete con;
87 printf("Execute failed\n");
88 return rv;
90 if (stmt->fetch()== NULL) {
91 delete stmt; delete con;
92 printf("Fetch failed\n");
93 return ErrSysInternal;
95 con->commit();
96 stmt->free();
97 printf("\nNumber of Records:\n");
98 printf("-------------------+-------------------+-------------------+\n");
99 printf(" Data | In CSQL | In TargetDB |\n");
100 printf("-------------------+-------------------+-------------------+\n");
101 printf(" No. Of Records | %-6ld | %-6d |\n", numTuples, count);
102 printf("-------------------+-------------------+-------------------+\n");
103 delete stmt; delete con;
104 return OK;
107 DbRetVal verifyMismatchingRecords(const char *tblName, int option)
109 Connection conn;
110 DbRetVal rv = conn.open("root", "manager");
111 if (rv != OK) return ErrSysInit;
112 DatabaseManager *dbMgr = (DatabaseManager *) conn.getDatabaseManager();
113 if (dbMgr == NULL) {
114 conn.close();
115 printf("Auth failed\n");
116 return ErrSysInit;
118 Table *table = dbMgr->openTable(tblName);
119 if(table == NULL) {
120 conn.close();
121 printf("Table \'%s\' does not exist", tblName);
122 return ErrNotExists;
125 char statement[200];
126 AbsSqlConnection *trgtDbCon = SqlFactory::createConnection(CSqlAdapter);
127 rv = trgtDbCon->connect("root", "manager");
128 if (rv != OK) {
129 dbMgr->closeTable(table); conn.close();
130 delete trgtDbCon; return ErrSysInit;
132 AbsSqlStatement *trgtDbStmt = SqlFactory::createStatement(CSqlAdapter);
133 trgtDbStmt->setConnection(trgtDbCon);
135 char fieldName[IDENTIFIER_LENGTH];
136 fieldName[0] = '\0';
137 SqlOdbcStatement *ostmt = (SqlOdbcStatement*) trgtDbStmt;
138 ostmt->getPrimaryKeyFieldName((char*)tblName, fieldName);
139 if (fieldName[0] == '\0') {
140 dbMgr->closeTable(table); conn.close();
141 delete trgtDbStmt; delete trgtDbCon;
142 printf("Primary key does not exist on table %s\n", tblName);
143 return ErrNotExists;
145 printf("\nPrimary key field name is \'%s\'\n", fieldName);
146 //FieldInfo *fldInfo = new FieldInfo();
147 //table->getFieldInfo(fieldName, fldInfo);
148 //if(! fldInfo->isPrimary) { printError(ErrBadArg, "\'%s\' is not a primary key field", fldName); return ErrBadArg; }
149 int csqlVal = 0;
151 //List for primary key field values present in csql server
152 List valListInCsql;
153 table->bindFld(fieldName, &csqlVal);
154 conn.startTransaction();
155 table->setCondition(NULL);
156 rv = table->execute();
157 if(rv != OK) {
158 dbMgr->closeTable(table); conn.close();
159 delete trgtDbStmt; delete trgtDbCon;
160 printf("Execute failed\n");
161 return rv;
163 while(true) {
164 void* tuple = table->fetch(rv);
165 if (tuple == NULL) break;
166 PrimKeyFldVal *pkFldVal = new PrimKeyFldVal();
167 pkFldVal->val = csqlVal;
168 pkFldVal->inCsql = true;
169 pkFldVal->inTrgtDb = true;
170 valListInCsql.append(pkFldVal);
172 table->close();
173 conn.commit();
174 int trgtDbVal = 0;
175 int rows = 0;
177 // List for primary key field values present in target DB
178 List valListInTrgtDb;
179 sprintf(statement, "select %s from %s;", fieldName, tblName);
180 rv = trgtDbCon->beginTrans();
181 rv = trgtDbStmt->prepare(statement);
182 if(rv != OK) {
183 dbMgr->closeTable(table); conn.close();
184 delete trgtDbStmt; delete trgtDbCon;
185 printf("Prepare failed\n");
186 return rv;
188 trgtDbStmt->bindField(1, &trgtDbVal);
189 rv = trgtDbStmt->execute(rows);
190 if(rv != OK) {
191 dbMgr->closeTable(table); conn.close();
192 delete trgtDbStmt; delete trgtDbCon;
193 printf("Execute failed\n");
194 return rv;
196 while (trgtDbStmt->fetch() != NULL) {
197 PrimKeyFldVal *pkFldVal = new PrimKeyFldVal();
198 pkFldVal->val = trgtDbVal;
199 pkFldVal->inCsql = true;
200 pkFldVal->inTrgtDb = true;
201 valListInTrgtDb.append(pkFldVal);
203 trgtDbCon->commit();
204 trgtDbStmt->free();
206 // List for primary key field values present in either of the databases
207 List diffInValList;
209 // List for primary key field values present in both the databases
210 List sameInBothDbList;
211 ListIterator csqlValIter = valListInCsql.getIterator();
212 ListIterator trgtDbValIter = valListInTrgtDb.getIterator();
213 PrimKeyFldVal *csqlelem, *trgtdbelem;
214 while( (csqlelem = (PrimKeyFldVal *) csqlValIter.nextElement()) != NULL) {
215 while ( (trgtdbelem = (PrimKeyFldVal *) trgtDbValIter.nextElement()) != NULL) {
216 if (csqlelem->val == trgtdbelem->val) {
217 PrimKeyFldVal *elm = new PrimKeyFldVal();
218 *elm = *csqlelem;
219 sameInBothDbList.append(elm);
220 trgtDbValIter.reset();
221 break;
224 if (trgtdbelem == NULL) {
225 csqlelem->inTrgtDb = false;
226 PrimKeyFldVal *elm = new PrimKeyFldVal();
227 *elm = * csqlelem;
228 diffInValList.append(elm);
229 trgtDbValIter.reset();
232 csqlValIter.reset();
233 while((trgtdbelem = (PrimKeyFldVal *)trgtDbValIter.nextElement()) != NULL) {
234 while((csqlelem = (PrimKeyFldVal *)csqlValIter.nextElement()) != NULL) {
235 if (trgtdbelem->val == csqlelem->val) {
236 csqlValIter.reset();
237 break;
240 if (csqlelem == NULL) {
241 trgtdbelem->inCsql = false;
242 PrimKeyFldVal *elm = new PrimKeyFldVal();
243 *elm = *trgtdbelem;
244 diffInValList.append(elm);
245 csqlValIter.reset();
249 // Sorting the primary key field values present in either of the databases
250 list <PrimKeyFldVal *> li;
251 ListIterator diffValIter = diffInValList.getIterator();
252 bool missingRecords = false;
253 printf("\nMissing Records: Marked by \'X\'\n");
254 printf("-------------------+-------------------+-------------------+\n");
255 printf(" Primary Key | In CSQL | In Target DB |\n");
256 printf("-------------------+-------------------+-------------------+\n");
257 if (diffInValList.size()) {
258 missingRecords = true;
259 PrimKeyFldVal *elem = NULL;
260 while ((elem = (PrimKeyFldVal *) diffValIter.nextElement()) != NULL )
261 li.push_back(elem);
263 list<PrimKeyFldVal *>::iterator it;
264 li.sort(cmp);
265 for (it = li.begin(); it != li.end(); it++) {
266 if ((*it)->inCsql == false)
267 printf(" %-6d | X | |\n", (*it)->val);
268 else if ((*it)->inTrgtDb == false)
269 printf(" %-6d | | X |\n", (*it)->val);
271 printf("-------------------+-------------------+-------------------+\n");
273 else {
274 printf(" No missing Records in either of the databases |\n");
275 printf("-------------------+-------------------+-------------------+\n");
278 // Need to clean up the mess that is no more required
279 PrimKeyFldVal *pkFldVal = NULL;
280 while ((pkFldVal = (PrimKeyFldVal *) csqlValIter.nextElement()) != NULL)
281 delete pkFldVal;
282 valListInCsql.reset();
283 while ((pkFldVal = (PrimKeyFldVal *) trgtDbValIter.nextElement()) != NULL)
284 delete pkFldVal;
285 valListInTrgtDb.reset();
286 while ((pkFldVal = (PrimKeyFldVal *) diffValIter.nextElement()) != NULL)
287 delete pkFldVal;
288 diffInValList.reset();
291 if (option == 4) {
292 AbsSqlConnection *csqlCon = SqlFactory::createConnection(CSql);
293 rv = csqlCon->connect("root", "manager");
294 if (rv != OK) {
295 dbMgr->closeTable(table); conn.close();
296 delete trgtDbStmt; delete trgtDbCon;
297 delete csqlCon; return ErrSysInit;
299 AbsSqlStatement *csqlStmt = SqlFactory::createStatement(CSql);
300 csqlStmt->setConnection(csqlCon);
302 //statement to fetch the values from the database
303 sprintf(statement, "select * from %s where %s = ?;", tblName, fieldName);
304 rv = csqlStmt->prepare(statement);
305 if(rv != OK) {
306 dbMgr->closeTable(table); conn.close();
307 delete trgtDbStmt; delete trgtDbCon;
308 delete csqlStmt; delete csqlCon;
309 printf("Prepare failed\n");
310 return rv;
313 // need to bind each field with buffer which is list of field values
314 List fldNameList = table->getFieldNameList();
315 ListIterator iter = fldNameList.getIterator();
316 Identifier *fname = NULL;
317 FieldInfo *fldInfo = new FieldInfo();
318 List fieldValueList;
320 // List to hold all the records that are present in both the databases
321 List csqlRecordList;
322 int paramPos = 1;
323 while (iter.hasElement()) {
324 fname = (Identifier *) iter.nextElement();
325 if (NULL == fname) {
326 dbMgr->closeTable(table); conn.close();
327 delete trgtDbStmt; delete trgtDbCon;
328 delete csqlStmt; delete csqlCon;
329 table = NULL;
330 delete fldInfo;
331 printf("Should never happen. Field Name list has NULL\n");
332 return ErrSysFatal;
334 rv = table->getFieldInfo(fname->name, fldInfo);
335 if (ErrNotFound == rv) {
336 dbMgr->closeTable(table); conn.close();
337 delete trgtDbStmt; delete trgtDbCon;
338 delete csqlStmt; delete csqlCon;
339 table = NULL;
340 delete fldInfo;
341 printf("Field %s does not exist in table\n",
342 fname->name);
343 return ErrSyntaxError;
345 FldVal *fldVal = new FldVal();
346 fldVal->type = fldInfo->type;
347 fldVal->length = fldInfo->length;
348 fldVal->value = AllDataType::alloc(fldInfo->type, fldInfo->length);
349 fieldValueList.append(fldVal);
350 csqlStmt->bindField(paramPos++, fldVal->value);
352 delete fldInfo;
354 // WHERE parameter should be binded with the primary key field value of the list that is present in both the databases
355 ListIterator sameValIter = sameInBothDbList.getIterator();
356 PrimKeyFldVal *sameElem = NULL;
357 while((sameElem = (PrimKeyFldVal *)sameValIter.nextElement()) != NULL) {
358 csqlCon->beginTrans();
359 csqlStmt->setIntParam(1, sameElem->val);
360 rv = csqlStmt->execute(rows);
361 if(rv != OK) {
362 dbMgr->closeTable(table); conn.close();
363 delete trgtDbStmt; delete trgtDbCon;
364 delete csqlStmt; delete csqlCon;
365 printf("Execute failed\n");
366 return rv;
368 while (csqlStmt->fetch() != NULL) {
369 Record *rec = new Record();
370 rec->val = sameElem->val;
371 ListIterator fldValIter = fieldValueList.getIterator();
372 while (fldValIter.hasElement()) {
373 FldVal *fldVal = (FldVal *) fldValIter.nextElement();
374 FldVal *fldValue = new FldVal();
375 fldValue->type = fldVal->type;
376 fldValue->length = fldVal->length;
377 fldValue->value = AllDataType::alloc(fldValue->type, fldValue->length);
378 AllDataType::copyVal(fldValue->value, fldVal->value, fldVal->type, fldVal->length);
379 rec->fldValList.append(fldValue);
381 csqlRecordList.append(rec);
383 csqlStmt->close();
384 csqlCon->commit();
386 csqlStmt->free();
388 //statement to fetch the values from the database
389 sprintf(statement, "select * from %s where %s = ?;", tblName, fieldName);
390 rv = trgtDbStmt->prepare(statement);
391 if(rv != OK) {
392 dbMgr->closeTable(table); conn.close();
393 delete csqlStmt; delete csqlCon;
394 delete trgtDbStmt; delete trgtDbCon;
395 printf("Prepare failed\n");
396 return rv;
399 // need to bind each field with buffer which is list of field values
400 ListIterator fldValIter = fieldValueList.getIterator();
401 fldValIter.reset();
402 List trgtDbRecordList;
403 paramPos = 1;
404 while (fldValIter.hasElement()) {
405 FldVal *fldVal = (FldVal *) fldValIter.nextElement();
406 trgtDbStmt->bindField(paramPos++, fldVal->value);
409 // WHERE parameter should be binded
410 sameValIter.reset();
411 while((sameElem = (PrimKeyFldVal *)sameValIter.nextElement()) != NULL) {
412 trgtDbCon->beginTrans();
413 trgtDbStmt->setIntParam(1, sameElem->val);
414 rv = trgtDbStmt->execute(rows);
415 if(rv != OK) {
416 dbMgr->closeTable(table); conn.close();
417 delete csqlStmt; delete csqlCon;
418 delete trgtDbStmt; delete trgtDbCon;
419 printf("Execute failed\n");
420 return rv;
422 while (trgtDbStmt->fetch() != NULL) {
423 Record *rec = new Record();
424 rec->val = sameElem->val;
425 fldValIter.reset();
426 while (fldValIter.hasElement()) {
427 FldVal *fldVal = (FldVal *) fldValIter.nextElement();
428 FldVal *fldValue = new FldVal();
429 fldValue->type = fldVal->type;
430 fldValue->length = fldVal->length;
431 fldValue->value = AllDataType::alloc(fldValue->type, fldValue->length);
432 AllDataType::copyVal(fldValue->value, fldVal->value, fldVal->type, fldVal->length);
433 rec->fldValList.append(fldValue);
435 trgtDbRecordList.append(rec);
437 trgtDbStmt->close();
438 trgtDbCon->commit();
440 trgtDbStmt->free();
442 // freeing the fieldValue buffer list which is not required any more
443 FldVal *fldVal = NULL;
444 while ((fldVal =(FldVal *) fldValIter.nextElement()) != NULL) {
445 free(fldVal->value);
446 delete fldVal;
448 fieldValueList.reset();
450 // freeing the field value list that is present in both the databases
451 PrimKeyFldVal *pkFldVal = NULL;
452 while ((pkFldVal = (PrimKeyFldVal *) sameValIter.nextElement()) != NULL)
453 delete pkFldVal;
454 sameInBothDbList.reset();
456 // sort the records based on Primary key that is present in both the databases
457 list <Record *> csqlRecList;
458 ListIterator csqlRecListIter = csqlRecordList.getIterator();
459 Record *elem;
460 while ((elem = (Record *) csqlRecListIter.nextElement()) != NULL )
461 csqlRecList.push_back(elem);
462 csqlRecList.sort(cmpRec);
464 list <Record *> trgtDbRecList;
465 ListIterator trgtDbRecListIter = trgtDbRecordList.getIterator();
466 while ((elem = (Record *) trgtDbRecListIter.nextElement()) != NULL )
467 trgtDbRecList.push_back(elem);
468 trgtDbRecList.sort(cmpRec);
470 int flag = 0;
471 bool isConsistent = true;
472 list<Record *>::iterator itCsql;
473 list<Record *>::iterator itTrgtDb;
474 iter.reset();
475 printf("\nInconsistent Records for the same key:\n");
476 printf("-------------------+-------------------+-------------------+-------------------+\n");
477 printf(" %-16s | %-16s | %-16s | %-16s |\n", "Primary Key", "Field Name", "In CSQL", "In Trgt DB");
478 printf("-------------------+-------------------+-------------------+-------------------+\n");
479 for (itCsql = csqlRecList.begin(), itTrgtDb = trgtDbRecList.begin();
480 itCsql != csqlRecList.end() && itTrgtDb != trgtDbRecList.end();
481 itCsql++, itTrgtDb++) {
482 ListIterator csqlIt = (ListIterator)((*itCsql)->fldValList).getIterator();
483 ListIterator trgtDbIt = (ListIterator)((*itTrgtDb)->fldValList).getIterator();
484 iter.reset();
485 flag = 0;
486 while (csqlIt.hasElement() && trgtDbIt.hasElement()) {
487 FldVal *csqlElem = (FldVal *) csqlIt.nextElement();
488 FldVal *trgtDbElem = (FldVal *) trgtDbIt.nextElement();
489 fname = (Identifier *) iter.nextElement();
490 if (AllDataType::compareVal(csqlElem->value, trgtDbElem->value, OpEquals, csqlElem->type, csqlElem->length) == false) {
491 isConsistent = false;
492 if (! flag) {
493 printf(" %-16d | %-16s | ", (*itCsql)->val, fname);
494 flag = 1;
496 else printf(" | %-16s | ", fname);
497 int cnt = AllDataType::printVal(csqlElem->value, csqlElem->type, csqlElem->length);
498 cnt = 17 - cnt;
499 while (cnt-- != 0)
500 printf(" ");
501 printf("| ");
502 cnt = AllDataType::printVal(trgtDbElem->value, trgtDbElem->type, trgtDbElem->length);
503 cnt = 17 - cnt;
504 while (cnt-- != 0)
505 printf(" ");
506 printf("|\n");
510 if (isConsistent == true && missingRecords == false)
511 printf(" The data is consistent in both the databases |\n");
512 else if (isConsistent == true && missingRecords == true)
513 printf(" The data is consistent for the records with the same key |\n");
514 printf("-------------------+-------------------+-------------------+-------------------+\n");
516 // clean up all the mess before leaving
517 csqlRecList.clear();
518 trgtDbRecList.clear();
520 iter.reset();
521 while ((fname = (Identifier *) iter.nextElement()) != NULL)
522 delete fname;
523 fldNameList.reset();
525 Record *item = NULL;
526 while((item = (Record *) csqlRecListIter.nextElement()) != NULL) {
527 ListIterator it = (ListIterator) item->fldValList.getIterator();
528 FldVal *fldVal = NULL;
529 while((fldVal = (FldVal *) it.nextElement()) != NULL) {
530 free (fldVal->value);
531 delete fldVal;
533 it.reset();
535 csqlRecordList.reset();
537 while((item = (Record *) trgtDbRecListIter.nextElement()) != NULL) {
538 ListIterator it = (ListIterator) item->fldValList.getIterator();
539 FldVal *fldVal = NULL;
540 while((fldVal = (FldVal *) it.nextElement()) != NULL) {
541 free (fldVal->value);
542 delete fldVal;
544 it.reset();
546 trgtDbRecordList.reset();
547 delete csqlStmt; delete csqlCon;
550 dbMgr->closeTable(table);
551 conn.close();
552 delete trgtDbStmt; delete trgtDbCon;
553 return OK;
556 int main(int argc, char **argv)
558 char username[IDENTIFIER_LENGTH];
559 username [0] = '\0';
560 char password[IDENTIFIER_LENGTH];
561 password [0] = '\0';
562 int c = 0, opt = 10;
563 char tableName[IDENTIFIER_LENGTH];
564 tableName[0] = '\0';
565 bool tableNameSpecified = false;
567 while ((c = getopt(argc, argv, "U:P:t:pf?")) != EOF)
569 switch (c)
571 case 'U' : { strcpy(username, argv[optind - 1]); opt=10; break; }
572 case 'P' : { strcpy(password, argv[optind - 1]); opt=10; break; }
573 case 't' : { strcpy(tableName, argv[optind - 1]);
574 if (opt==10) opt = 2;
575 tableNameSpecified = true;
576 break;
578 case 'p' : { opt = 3; break; } //verify at primary key level
579 case 'f' : { opt = 4; break; } //verify at record level
580 case '?' : { opt = 10; break; } //print help
581 default: opt=10;
584 }//while options
585 if (opt == 10) {
586 printUsage();
587 return 0;
590 if (!tableNameSpecified) {
591 printf("Table name is not specified. Check usage with ?\n");
592 return 1;
595 //printf("%s %s \n", username, password);
596 if (username[0] == '\0' )
598 strcpy(username, "root");
599 strcpy(password, "manager");
602 Connection conn;
603 DbRetVal rv = conn.open(username, password);
604 if (rv != OK) {
605 printf("Authentication failed\n");
606 return 1;
609 DatabaseManager *dbMgr = (DatabaseManager *) conn.getDatabaseManager();
610 if (dbMgr == NULL) {
611 conn.close();
612 printf("could not connect to the database\n");
613 return 2;
617 Table *table = dbMgr->openTable(tableName);
618 if(table == NULL) {
619 conn.close();
620 printf("Table \'%s\' does not exist\n", tableName);
621 return 3;
624 FILE *fp;
625 fp = fopen(Conf::config.getTableConfigFile(),"r");
626 if( fp == NULL ) {
627 dbMgr->closeTable(table);
628 conn.close();
629 printf("cachetable.conf file does not exist\n");
630 return 4;
632 char tablename[IDENTIFIER_LENGTH];
633 int mode;
634 bool filePresent = false;
635 while(!feof(fp)) {
636 fscanf(fp, "%d:%s\n", &mode, tablename);
637 if (mode ==2 ) //just replicated table and not cached
638 continue;
639 if (strcmp(tableName, tablename) == 0) {
640 filePresent = true;
641 break;
644 fclose(fp);
645 long numTuples = table->numTuples();
646 dbMgr->closeTable(table);
647 conn.close();
649 if (filePresent == false) { printf("The table \'%s\' is not cached\n", tableName);
650 return 5;
653 if (opt == 2) {
654 rv = verifyCount(tableName, numTuples);
655 if (rv != OK) return 6;
658 if (opt == 3 || opt == 4) {
659 rv = verifyCount(tableName, numTuples);
660 if (rv != OK) return 7;
661 rv = verifyMismatchingRecords(tableName, opt);
662 if (rv != OK) return 8;
664 return 0;