]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
The dbselftest utility now generates hashes in the selftest table with --init.
authordrh <drh@noemail.net>
Tue, 7 Feb 2017 20:51:38 +0000 (20:51 +0000)
committerdrh <drh@noemail.net>
Tue, 7 Feb 2017 20:51:38 +0000 (20:51 +0000)
It also accepts multiple database files on the command-line.

FossilOrigin-Name: e68829c9bbc69bf4a0dc057e0a6e977f2fac79be

manifest
manifest.uuid
test/dbselftest.c

index 854ed844591b104cf5e57370ad087797b222a0bc..a37a9e38bf12165aad246b086e989baffffb7c25 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Omit\sfts5fault1.test\sfrom\sthe\sinmemory_journal\spermutation.
-D 2017-02-07T19:36:14.794
+C The\sdbselftest\sutility\snow\sgenerates\shashes\sin\sthe\sselftest\stable\swith\s--init.\nIt\salso\saccepts\smultiple\sdatabase\sfiles\son\sthe\scommand-line.
+D 2017-02-07T20:51:38.461
 F Makefile.in edb6bcdd37748d2b1c3422ff727c748df7ffe918
 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
 F Makefile.msc ba953c8921fc7e18333f61898007206de7e23964
@@ -636,7 +636,7 @@ F test/cursorhint.test 7bc346788390475e77a345da2b92270d04d35856
 F test/cursorhint2.test 8457e93d97f665f23f97cdbc8477d16e3480331b
 F test/date.test a6a5a48b90907bca9fbcc79a30be5a715c1ab2fc
 F test/dbfuzz.c 8cc2bdb818b4483a052f9f80f96be74cbd9a6e1d
-F test/dbselftest.c eeb95d09932b4dc79e5886bd8cf5d812e7d28d94
+F test/dbselftest.c 4bf86fe04dc2d0d58dabc5e6b7a06fa4ef7827ea
 F test/dbstatus.test 73149851b3aff14fc6db478e58f9083a66422cf5
 F test/dbstatus2.test e93ab03bfae6d62d4d935f20de928c19ca0ed0ab
 F test/default.test 0cb49b1c315a0d81c81d775e407f66906a2a604d
@@ -1555,7 +1555,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P be82d5ae20ba62a165bdc28766a8dc8049abcac6
-R 4c6aa653d353c19152ed7b2aaba4bc83
-U dan
-Z 450aa5587fc5eb1e148efd8100bf109e
+P cb1e83f9583bf93ce7583d9f5e97272e2d43cfb8
+R 64e5750ab44994d5b187d93e5c4a1318
+U drh
+Z f3a9d081d357071c2c4fc7a23224549e
index 026b45bd0389fe893810925268a3276f0fc60463..6596630605ac0bf1151c5ae6ada544651dded151 100644 (file)
@@ -1 +1 @@
-cb1e83f9583bf93ce7583d9f5e97272e2d43cfb8
\ No newline at end of file
+e68829c9bbc69bf4a0dc057e0a6e977f2fac79be
\ No newline at end of file
index 1c6f8d8b9f647411f989b1655772fdcdb5f583a0..d8a9c331dea6959654bb83655f44cb011886b64c 100644 (file)
@@ -13,7 +13,7 @@
 ** This program implements an SQLite database self-verification utility.
 ** Usage:
 ** 
-**       dbselftest DATABASE
+**       dbselftest DATABASE ...
 **
 ** This program reads the "selftest" table in DATABASE, in rowid order,
 ** and runs each of the tests described there, reporting results at the
@@ -44,7 +44,7 @@
 #include "sqlite3.h"
 
 static const char zHelp[] =
-  "Usage: dbselftest [OPTIONS] DBFILE\n"
+  "Usage: dbselftest [OPTIONS] DBFILE ...\n"
   "\n"
   "    --init         Create the selftest table\n"
   "    -q             Suppress most output.  Errors only\n"
@@ -294,6 +294,101 @@ static void sha1Func(
   sqlite3_result_text(context, zOut, 40, SQLITE_TRANSIENT);
 }
 
+/*
+** Run a prepared statement and compute the SHA1 hash on the
+** result rows.
+*/
+static void sha1RunStatement(SHA1Context *pCtx, sqlite3_stmt *pStmt){
+  int nCol = sqlite3_column_count(pStmt);
+  const char *z = sqlite3_sql(pStmt);
+  int n = (int)strlen(z);
+
+  hash_step_vformat(pCtx,"S%d:",n);
+  hash_step(pCtx,(unsigned char*)z,n);
+
+  /* Compute a hash over the result of the query */
+  while( SQLITE_ROW==sqlite3_step(pStmt) ){
+    int i;
+    hash_step(pCtx,(const unsigned char*)"R",1);
+    for(i=0; i<nCol; i++){
+      switch( sqlite3_column_type(pStmt,i) ){
+        case SQLITE_NULL: {
+          hash_step(pCtx, (const unsigned char*)"N",1);
+          break;
+        }
+        case SQLITE_INTEGER: {
+          sqlite3_uint64 u;
+          int j;
+          unsigned char x[9];
+          sqlite3_int64 v = sqlite3_column_int64(pStmt,i);
+          memcpy(&u, &v, 8);
+          for(j=8; j>=1; j--){
+            x[j] = u & 0xff;
+            u >>= 8;
+          }
+          x[0] = 'I';
+          hash_step(pCtx, x, 9);
+          break;
+        }
+        case SQLITE_FLOAT: {
+          sqlite3_uint64 u;
+          int j;
+          unsigned char x[9];
+          double r = sqlite3_column_double(pStmt,i);
+          memcpy(&u, &r, 8);
+          for(j=8; j>=1; j--){
+            x[j] = u & 0xff;
+            u >>= 8;
+          }
+          x[0] = 'F';
+          hash_step(pCtx,x,9);
+          break;
+        }
+        case SQLITE_TEXT: {
+          int n2 = sqlite3_column_bytes(pStmt, i);
+          const unsigned char *z2 = sqlite3_column_text(pStmt, i);
+          hash_step_vformat(pCtx,"T%d:",n2);
+          hash_step(pCtx, z2, n2);
+          break;
+        }
+        case SQLITE_BLOB: {
+          int n2 = sqlite3_column_bytes(pStmt, i);
+          const unsigned char *z2 = sqlite3_column_blob(pStmt, i);
+          hash_step_vformat(pCtx,"B%d:",n2);
+          hash_step(pCtx, z2, n2);
+          break;
+        }
+      }
+    }
+  }
+}
+
+/*
+** Run one or more statements of SQL.  Compute a SHA1 hash of the output.
+*/
+static int sha1Exec(
+  sqlite3 *db,          /* Run against this database connection */
+  const char *zSql,     /* The SQL to be run */
+  char *zOut            /* Store the SHA1 hash as hexadecimal in this buffer */
+){
+  sqlite3_stmt *pStmt = 0;    /* A prepared statement */
+  int rc;                     /* Result of an API call */
+  SHA1Context cx;             /* The SHA1 hash context */
+
+  hash_init(&cx);
+  while( zSql[0] ){
+    rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zSql);
+    if( rc ){
+      sqlite3_finalize(pStmt);
+      return rc;
+    }
+    sha1RunStatement(&cx, pStmt);
+    sqlite3_finalize(pStmt);
+  }
+  hash_finish(&cx, zOut);
+  return SQLITE_OK;
+}
+
 /*
 ** Implementation of the sha1_query(SQL) function.
 **
@@ -314,11 +409,7 @@ static void sha1QueryFunc(
   sqlite3 *db = sqlite3_context_db_handle(context);
   const char *zSql = (const char*)sqlite3_value_text(argv[0]);
   sqlite3_stmt *pStmt = 0;
-  int nCol;                   /* Number of columns in the result set */
-  int i;                      /* Loop counter */
   int rc;
-  int n;
-  const char *z;
   SHA1Context cx;
   char zOut[44];
 
@@ -342,66 +433,7 @@ static void sha1QueryFunc(
       sqlite3_free(zMsg);
       return;
     }
-    nCol = sqlite3_column_count(pStmt);
-    z = sqlite3_sql(pStmt);
-    n = (int)strlen(z);
-    hash_step_vformat(&cx,"S%d:",n);
-    hash_step(&cx,(unsigned char*)z,n);
-
-    /* Compute a hash over the result of the query */
-    while( SQLITE_ROW==sqlite3_step(pStmt) ){
-      hash_step(&cx,(const unsigned char*)"R",1);
-      for(i=0; i<nCol; i++){
-        switch( sqlite3_column_type(pStmt,i) ){
-          case SQLITE_NULL: {
-            hash_step(&cx, (const unsigned char*)"N",1);
-            break;
-          }
-          case SQLITE_INTEGER: {
-            sqlite3_uint64 u;
-            int j;
-            unsigned char x[9];
-            sqlite3_int64 v = sqlite3_column_int64(pStmt,i);
-            memcpy(&u, &v, 8);
-            for(j=8; j>=1; j--){
-              x[j] = u & 0xff;
-              u >>= 8;
-            }
-            x[0] = 'I';
-            hash_step(&cx, x, 9);
-            break;
-          }
-          case SQLITE_FLOAT: {
-            sqlite3_uint64 u;
-            int j;
-            unsigned char x[9];
-            double r = sqlite3_column_double(pStmt,i);
-            memcpy(&u, &r, 8);
-            for(j=8; j>=1; j--){
-              x[j] = u & 0xff;
-              u >>= 8;
-            }
-            x[0] = 'F';
-            hash_step(&cx,x,9);
-            break;
-          }
-          case SQLITE_TEXT: {
-            int n2 = sqlite3_column_bytes(pStmt, i);
-            const unsigned char *z2 = sqlite3_column_text(pStmt, i);
-            hash_step_vformat(&cx,"T%d:",n2);
-            hash_step(&cx, z2, n2);
-            break;
-          }
-          case SQLITE_BLOB: {
-            int n2 = sqlite3_column_bytes(pStmt, i);
-            const unsigned char *z2 = sqlite3_column_blob(pStmt, i);
-            hash_step_vformat(&cx,"B%d:",n2);
-            hash_step(&cx, z2, n2);
-            break;
-          }
-        }
-      }
-    }
+    sha1RunStatement(&cx, pStmt);
     sqlite3_finalize(pStmt);
   }
   hash_finish(&cx, zOut);
@@ -439,7 +471,7 @@ static void strAppend(Str *p, const char *z){
       exit(1);
     }
   }
-  memcpy(p->z+n, z, n+1);
+  memcpy(p->z+p->n, z, n+1);
   p->n += n;
 }
 
@@ -461,9 +493,138 @@ static int execCallback(void *pStr, int argc, char **argv, char **colv){
   return 0;
 }
 
+/*
+** Run an SQL statement constructing using sqlite3_vmprintf().
+** Return the number of errors.
+*/
+static int runSql(sqlite3 *db, const char *zFormat, ...){
+  char *zSql;
+  char *zErr = 0;
+  int rc;
+  int nErr = 0;
+  va_list ap;
+
+  va_start(ap, zFormat);
+  zSql = sqlite3_vmprintf(zFormat, ap);
+  va_end(ap);
+  if( zSql==0 ){
+    printf("Out of memory\n");
+    exit(1);
+  }
+  rc = sqlite3_exec(db, zSql, 0, 0, &zErr);
+  if( rc || zErr ){
+    printf("SQL error in [%s]: code=%d: %s\n", zSql, rc, zErr);
+    nErr++;
+  }
+  sqlite3_free(zSql);
+  return nErr;
+}
+
+/*
+** Generate a prepared statement using a formatted string.
+*/
+static sqlite3_stmt *prepareSql(sqlite3 *db, const char *zFormat, ...){
+  char *zSql;
+  int rc;
+  sqlite3_stmt *pStmt = 0;
+  va_list ap;
+
+  va_start(ap, zFormat);
+  zSql = sqlite3_vmprintf(zFormat, ap);
+  va_end(ap);
+  if( zSql==0 ){
+    printf("Out of memory\n");
+    exit(1);
+  }
+  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+  if( rc ){
+    printf("SQL error in [%s]: code=%d: %s\n", zSql, rc, sqlite3_errmsg(db));
+    sqlite3_finalize(pStmt);
+    pStmt = 0;
+  }
+  sqlite3_free(zSql);
+  return pStmt;
+}
+
+/*
+** Construct the standard selftest configuration for the database.
+*/
+static int buildSelftestTable(sqlite3 *db){
+  int rc;
+  sqlite3_stmt *pStmt;
+  int tno = 110;
+  char *zSql;
+  char zHash[50];
+
+  rc = runSql(db,
+     "CREATE TABLE IF NOT EXISTS selftest(\n"
+     "  tno INTEGER PRIMARY KEY,  -- test number\n"
+     "  op TEXT,                  -- what kind of test\n"
+     "  sql TEXT,                 -- SQL text for the test\n"
+     "  ans TEXT                  -- expected answer\n"
+     ");"
+     "INSERT INTO selftest"
+     " VALUES(100,'memo','Hashes generated using --init',NULL);"
+  );
+  if( rc ) return 1;
+  tno = 110;
+  zSql = "SELECT type,name,tbl_name,sql FROM sqlite_master";
+  sha1Exec(db, zSql, zHash);
+  rc = runSql(db, 
+      "INSERT INTO selftest(tno,op,sql,ans)"
+      " VALUES(%d,'sha1',%Q,%Q)", tno, zSql, zHash);
+  tno += 10;
+  pStmt = prepareSql(db,
+    "SELECT lower(name) FROM sqlite_master"
+    " WHERE type='table' AND sql NOT GLOB 'CREATE VIRTUAL*'"
+    "   AND name<>'selftest'"
+    " ORDER BY 1");
+  if( pStmt==0 ) return 1;
+  while( SQLITE_ROW==sqlite3_step(pStmt) ){
+    zSql = sqlite3_mprintf("SELECT * FROM \"%w\" NOT INDEXED",
+                            sqlite3_column_text(pStmt, 0));
+    if( zSql==0 ){
+      printf("Of of memory\n");
+      exit(1);
+    }
+    sha1Exec(db, zSql, zHash);
+    rc = runSql(db,
+      "INSERT INTO selftest(tno,op,sql,ans)"
+      " VALUES(%d,'sha1',%Q,%Q)", tno, zSql, zHash);
+    tno += 10;
+    sqlite3_free(zSql);
+    if( rc ) break;
+  }
+  sqlite3_finalize(pStmt);
+  if( rc ) return 1;
+  rc = runSql(db,
+     "INSERT INTO selftest(tno,op,sql,ans)"
+     " VALUES(%d,'run','PRAGMA integrity_check','ok');", tno);
+  if( rc ) return 1;
+  return rc;
+}
+
+/*
+** Return true if the named table exists
+*/
+static int tableExists(sqlite3 *db, const char *zTab){
+  return sqlite3_table_column_metadata(db, "main", zTab, 0, 0, 0, 0, 0, 0)
+            == SQLITE_OK;
+}
+
+/*
+** Default selftest table content, for use when there is no selftest table
+*/
+static char *azDefaultTest[] = {
+   0, 0, 0, 0,
+   "0", "memo", "Missing SELFTEST table - default checks only", "",
+   "1", "run", "PRAGMA integrity_check", "ok"
+};
+
 int main(int argc, char **argv){
   int eVolume = VOLUME_LOW;    /* How much output to display */
-  const char *zDb = 0;         /* Name of the database file */
+  const char **azDb = 0;       /* Name of the database file */
+  int nDb = 0;                 /* Number of database files to check */
   int doInit = 0;              /* True if --init is present */
   sqlite3 *db = 0;             /* Open database connection */
   int rc;                      /* Return code from API calls */
@@ -472,7 +633,9 @@ int main(int argc, char **argv){
   int nRow = 0, nCol = 0;      /* Rows and columns in azTest[] */
   int i;                       /* Loop counter */
   int nErr = 0;                /* Number of errors */
+  int iDb;                     /* Loop counter for databases */
   Str str;                     /* Result accumulator */
+  int nTest = 0;               /* Number of tests run */
 
   for(i=1; i<argc; i++){
     const char *z = argv[i];
@@ -496,56 +659,18 @@ int main(int argc, char **argv){
                argv[i]);
         return 1;
       }
-    }else if( zDb!=0 ){
-      printf("More than one database specified.  Use --help for more info.\n");
-      return 1;
     }else{
-      zDb = argv[i];
-    }
-  }
-  if( zDb==0 ){
-    printf("No database specified.  Use --help for more info\n");
-    return 1;
-  }
-  rc = sqlite3_open(zDb, &db);
-  if( rc ){
-    printf("Cannot open \"%s\": %s\n", zDb, sqlite3_errmsg(db));
-    return 1;
-  }
-  rc = sqlite3_create_function(db, "sha1", 1, SQLITE_UTF8, 0,
-                               sha1Func, 0, 0);
-  if( rc==SQLITE_OK ){
-    rc = sqlite3_create_function(db, "sha1_query", 1, SQLITE_UTF8, 0,
-                                 sha1QueryFunc, 0, 0);
-  }
-  if( rc ){
-    printf("Initialization error: %s\n", sqlite3_errmsg(db));
-    sqlite3_close(db);
-    return 1;
-  }
-  if( doInit ){
-    rc = sqlite3_exec(db,
-      "CREATE TABLE IF NOT EXISTS selftest(\n"
-      "  tno INTEGER PRIMARY KEY,  -- test number\n"
-      "  op TEXT,                  -- what kind of test\n"
-      "  sql TEXT,                 -- SQL text for the test\n"
-      "  ans TEXT                  -- expected answer\n"
-      ");", 0, 0, &zErrMsg);
-    if( rc || zErrMsg ){
-      printf("CREATE TABLE selftest failed: %s\n", zErrMsg);
-      sqlite3_close(db);
-      return 1;
+      nDb++;
+      azDb = sqlite3_realloc(azDb, nDb*sizeof(azDb[0]));
+      if( azDb==0 ){
+        printf("out of memory\n");
+        exit(1);
+      }
+      azDb[nDb-1] = argv[i];
     }
   }
-  if( sqlite3_table_column_metadata(db,"main","selftest","sql",0,0,0,0,0) ){
-    printf("No such table: selftest\nSee the --init option.\n");
-    return 1;
-  }
-  rc = sqlite3_get_table(db, 
-      "SELECT tno,op,sql,ans FROM selftest ORDRE BY tno",
-      &azTest, &nRow, &nCol, &zErrMsg);
-  if( rc || zErrMsg ){
-    printf("Error querying selftest: %s\n", zErrMsg);
+  if( nDb==0 ){
+    printf("No databases specified.  Use --help for more info\n");
     return 1;
   }
   if( eVolume>=VOLUME_LOW ){
@@ -553,53 +678,109 @@ int main(int argc, char **argv){
   }
   memset(&str, 0, sizeof(str));
   strAppend(&str, "\n");
-  for(i=1; i<=nRow; i++){
-    int tno = atoi(azTest[i*nCol]);
-    const char *zOp = azTest[i*nCol+1];
-    const char *zSql = azTest[i*nCol+2];
-    const char *zAns = azTest[i*nCol+3];
-
-    if( eVolume>=VOLUME_ECHO ){
-      char *zQuote = sqlite3_mprintf("%q", zSql);
-      printf("%d: %s %s\n", tno, zOp, zSql);
-      sqlite3_free(zQuote);
+  for(iDb=0; iDb<nDb; iDb++, sqlite3_close(db)){
+    rc = sqlite3_open_v2(azDb[iDb], &db, 
+          doInit ? SQLITE_OPEN_READWRITE : SQLITE_OPEN_READONLY, 0);
+    if( rc ){
+      printf("Cannot open \"%s\": %s\n", azDb[iDb], sqlite3_errmsg(db));
+      return 1;
     }
-    if( strcmp(zOp,"memo")==0 ){
-      if( eVolume>=VOLUME_LOW ){
-        printf("%s\n", zSql);
+    rc = sqlite3_create_function(db, "sha1", 1, SQLITE_UTF8, 0,
+                                 sha1Func, 0, 0);
+    if( rc==SQLITE_OK ){
+      rc = sqlite3_create_function(db, "sha1_query", 1, SQLITE_UTF8, 0,
+                                   sha1QueryFunc, 0, 0);
+    }
+    if( rc ){
+      printf("Initialization error: %s\n", sqlite3_errmsg(db));
+      sqlite3_close(db);
+      return 1;
+    }
+    if( doInit && !tableExists(db, "selftest") ){
+       buildSelftestTable(db);
+    }
+    if( !tableExists(db, "selftest") ){
+      azTest = azDefaultTest;
+      nCol = 4;
+      nRow = 2;
+    }else{
+      rc = sqlite3_get_table(db, 
+          "SELECT tno,op,sql,ans FROM selftest ORDER BY tno",
+          &azTest, &nRow, &nCol, &zErrMsg);
+      if( rc || zErrMsg ){
+        printf("Error querying selftest: %s\n", zErrMsg);
+        sqlite3_free_table(azTest);
+        continue;
       }
-    }else
-    if( strcmp(zOp,"run")==0 ){
-      str.n = 0;
-      str.z[0] = 0;
-      zErrMsg = 0;
-      rc = sqlite3_exec(db, zSql, execCallback, &str, &zErrMsg);
-      if( eVolume>=VOLUME_VERBOSE ){
-        printf("Result: %s\n", str.z);
+    }
+    for(i=1; i<=nRow; i++){
+      int tno = atoi(azTest[i*nCol]);
+      const char *zOp = azTest[i*nCol+1];
+      const char *zSql = azTest[i*nCol+2];
+      const char *zAns = azTest[i*nCol+3];
+  
+      if( eVolume>=VOLUME_ECHO ){
+        char *zQuote = sqlite3_mprintf("%q", zSql);
+        printf("%d: %s %s\n", tno, zOp, zSql);
+        sqlite3_free(zQuote);
       }
-      if( rc || zErrMsg ){
-        nErr++;
-        if( eVolume>=VOLUME_ERROR_ONLY ){
-          printf("%d: error-code-%d: %s\n", tno, rc, zErrMsg);
+      if( strcmp(zOp,"memo")==0 ){
+        if( eVolume>=VOLUME_LOW ){
+          printf("%s: %s\n", azDb[iDb], zSql);
+        }
+      }else
+      if( strcmp(zOp,"sha1")==0 ){
+        char zOut[44];
+        rc = sha1Exec(db, zSql, zOut);
+        nTest++;
+        if( eVolume>=VOLUME_VERBOSE ){
+          printf("Result: %s\n", zOut);
         }
-        sqlite3_free(zErrMsg);
-      }else if( strcmp(zAns,str.z)!=0 ){
-        nErr++;
-        if( eVolume>=VOLUME_ERROR_ONLY ){
-          printf("%d: Expected: [%s]\n", tno, zAns);
-          printf("%d:      Got: [%s]\n", tno, str.z);
+        if( rc ){
+          nErr++;
+          if( eVolume>=VOLUME_ERROR_ONLY ){
+            printf("%d: error-code-%d: %s\n", tno, rc, sqlite3_errmsg(db));
+          }
+        }else if( strcmp(zAns,zOut)!=0 ){
+          nErr++;
+          if( eVolume>=VOLUME_ERROR_ONLY ){
+            printf("%d: Expected: [%s]\n", tno, zAns);
+            printf("%d:      Got: [%s]\n", tno, zOut);
+          }
+        }
+      }else
+      if( strcmp(zOp,"run")==0 ){
+        str.n = 0;
+        str.z[0] = 0;
+        zErrMsg = 0;
+        rc = sqlite3_exec(db, zSql, execCallback, &str, &zErrMsg);
+        nTest++;
+        if( eVolume>=VOLUME_VERBOSE ){
+          printf("Result: %s\n", str.z);
         }
+        if( rc || zErrMsg ){
+          nErr++;
+          if( eVolume>=VOLUME_ERROR_ONLY ){
+            printf("%d: error-code-%d: %s\n", tno, rc, zErrMsg);
+          }
+          sqlite3_free(zErrMsg);
+        }else if( strcmp(zAns,str.z)!=0 ){
+          nErr++;
+          if( eVolume>=VOLUME_ERROR_ONLY ){
+            printf("%d: Expected: [%s]\n", tno, zAns);
+            printf("%d:      Got: [%s]\n", tno, str.z);
+          }
+        }
+      }else
+      {
+        printf("Unknown operation \"%s\" on selftest line %d\n", zOp, tno);
+        return 1;
       }
-    }else
-    {
-      printf("Unknown operation \"%s\" on selftest line %d\n", zOp, tno);
-      return 1;
     }
+    if( azTest!=azDefaultTest ) sqlite3_free_table(azTest);
   }
-  sqlite3_free_table(azTest);
-  sqlite3_close(db);
   if( eVolume>=VOLUME_LOW || (nErr>0 && eVolume>=VOLUME_ERROR_ONLY) ){
-    printf("%d errors out of %d tests\n", nErr, nRow);
+    printf("%d errors out of %d tests on %d databases\n", nErr, nTest, nDb);
   }
   return nErr;
 }