/ Check-in [03a70c3d]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Merge the nx-devkit changes into trunk.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:03a70c3dae8d912fccd9d72c575dc372b198d238
User & Date: drh 2011-12-13 15:37:12
Context
2011-12-14
00:37
Treat a zero return value from the Win32 APIs MultiByteToWideChar and WideCharToMultiByte as an error condition. check-in: c65e5a36 user: mistachkin tags: trunk
2011-12-13
15:37
Merge the nx-devkit changes into trunk. check-in: 03a70c3d user: drh tags: trunk
15:25
Update the multiplex.test script to conform to that found in the "experimental" branch off of trunk. check-in: 2eb79efb user: drh tags: nx-devkit
2011-12-12
19:48
Fix os_unix.c so that, unless 8.3 filenames are actually in use, journal and wal file permissions are assigned correctly even if SQLITE_ENABLE_8_3_NAMES is defined. check-in: 169e1229 user: dan tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to src/loadext.c.

   621    621   ** Load all automatic extensions.
   622    622   **
   623    623   ** If anything goes wrong, set an error in the database connection.
   624    624   */
   625    625   void sqlite3AutoLoadExtensions(sqlite3 *db){
   626    626     int i;
   627    627     int go = 1;
          628  +  int rc;
   628    629     int (*xInit)(sqlite3*,char**,const sqlite3_api_routines*);
   629    630   
   630    631     wsdAutoextInit;
   631    632     if( wsdAutoext.nExt==0 ){
   632    633       /* Common case: early out without every having to acquire a mutex */
   633    634       return;
   634    635     }
................................................................................
   643    644         go = 0;
   644    645       }else{
   645    646         xInit = (int(*)(sqlite3*,char**,const sqlite3_api_routines*))
   646    647                 wsdAutoext.aExt[i];
   647    648       }
   648    649       sqlite3_mutex_leave(mutex);
   649    650       zErrmsg = 0;
   650         -    if( xInit && xInit(db, &zErrmsg, &sqlite3Apis) ){
   651         -      sqlite3Error(db, SQLITE_ERROR,
          651  +    if( xInit && (rc = xInit(db, &zErrmsg, &sqlite3Apis))!=0 ){
          652  +      sqlite3Error(db, rc,
   652    653               "automatic extension loading failed: %s", zErrmsg);
   653    654         go = 0;
   654    655       }
   655    656       sqlite3_free(zErrmsg);
   656    657     }
   657    658   }

Changes to src/main.c.

   253    253   ** while any part of SQLite is otherwise in use in any thread.  This
   254    254   ** routine is not threadsafe.  But it is safe to invoke this routine
   255    255   ** on when SQLite is already shut down.  If SQLite is already shut down
   256    256   ** when this routine is invoked, then this routine is a harmless no-op.
   257    257   */
   258    258   int sqlite3_shutdown(void){
   259    259     if( sqlite3GlobalConfig.isInit ){
          260  +#ifdef SQLITE_EXTRA_SHUTDOWN
          261  +    void SQLITE_EXTRA_SHUTDOWN(void);
          262  +    SQLITE_EXTRA_SHUTDOWN();
          263  +#endif
   260    264       sqlite3_os_end();
   261    265       sqlite3_reset_auto_extension();
   262    266       sqlite3GlobalConfig.isInit = 0;
   263    267     }
   264    268     if( sqlite3GlobalConfig.isPCacheInit ){
   265    269       sqlite3PcacheShutdown();
   266    270       sqlite3GlobalConfig.isPCacheInit = 0;
................................................................................
  2235   2239     */
  2236   2240     sqlite3Error(db, SQLITE_OK, 0);
  2237   2241     sqlite3RegisterBuiltinFunctions(db);
  2238   2242   
  2239   2243     /* Load automatic extensions - extensions that have been registered
  2240   2244     ** using the sqlite3_automatic_extension() API.
  2241   2245     */
  2242         -  sqlite3AutoLoadExtensions(db);
  2243   2246     rc = sqlite3_errcode(db);
  2244         -  if( rc!=SQLITE_OK ){
  2245         -    goto opendb_out;
         2247  +  if( rc==SQLITE_OK ){
         2248  +    sqlite3AutoLoadExtensions(db);
         2249  +    rc = sqlite3_errcode(db);
         2250  +    if( rc!=SQLITE_OK ){
         2251  +      goto opendb_out;
         2252  +    }
  2246   2253     }
  2247   2254   
  2248   2255   #ifdef SQLITE_ENABLE_FTS1
  2249   2256     if( !db->mallocFailed ){
  2250   2257       extern int sqlite3Fts1Init(sqlite3*);
  2251   2258       rc = sqlite3Fts1Init(db);
  2252   2259     }

Changes to src/test_multiplex.c.

   209    209   static int multiplexStrlen30(const char *z){
   210    210     const char *z2 = z;
   211    211     if( z==0 ) return 0;
   212    212     while( *z2 ){ z2++; }
   213    213     return 0x3fffffff & (int)(z2 - z);
   214    214   }
   215    215   
   216         -/*
   217         -** Create a temporary file name in zBuf.  zBuf must be big enough to
   218         -** hold at pOrigVfs->mxPathname characters.  This function departs
   219         -** from the traditional temporary name generation in the os_win
   220         -** and os_unix VFS in several ways, but is necessary so that 
   221         -** the file name is known for temporary files (like those used 
   222         -** during vacuum.)
   223         -**
   224         -** N.B. This routine assumes your underlying VFS is ok with using
   225         -** "/" as a directory seperator.  This is the default for UNIXs
   226         -** and is allowed (even mixed) for most versions of Windows.
   227         -*/
   228         -static int multiplexGetTempname(sqlite3_vfs *pOrigVfs, int nBuf, char *zBuf){
   229         -  static char zChars[] =
   230         -    "abcdefghijklmnopqrstuvwxyz"
   231         -    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
   232         -    "0123456789";
   233         -  int i,j;
   234         -  int attempts = 0;
   235         -  int exists = 0;
   236         -  int rc = SQLITE_ERROR;
   237         -
   238         -  /* Check that the output buffer is large enough for 
   239         -  ** pVfs->mxPathname characters.
   240         -  */
   241         -  if( pOrigVfs->mxPathname <= nBuf ){
   242         -    char *zTmp = sqlite3_malloc(pOrigVfs->mxPathname);
   243         -    if( zTmp==0 ) return SQLITE_NOMEM;
   244         -
   245         -    /* sqlite3_temp_directory should always be less than
   246         -    ** pVfs->mxPathname characters.
   247         -    */
   248         -    sqlite3_snprintf(pOrigVfs->mxPathname,
   249         -                     zTmp,
   250         -                     "%s/",
   251         -                     sqlite3_temp_directory ? sqlite3_temp_directory : ".");
   252         -    rc = pOrigVfs->xFullPathname(pOrigVfs, zTmp, nBuf, zBuf);
   253         -    sqlite3_free(zTmp);
   254         -    if( rc ) return rc;
   255         -
   256         -    /* Check that the output buffer is large enough for the temporary file 
   257         -    ** name.
   258         -    */
   259         -    j = multiplexStrlen30(zBuf);
   260         -    if( (j + 8 + 1 + 3 + 1) <= nBuf ){
   261         -      /* Make 3 attempts to generate a unique name. */
   262         -      do {
   263         -        attempts++;
   264         -        sqlite3_randomness(8, &zBuf[j]);
   265         -        for(i=0; i<8; i++){
   266         -          unsigned char uc = (unsigned char)zBuf[j+i];
   267         -          zBuf[j+i] = (char)zChars[uc%(sizeof(zChars)-1)];
   268         -        }
   269         -        memcpy(&zBuf[j+i], ".tmp", 5);
   270         -        rc = pOrigVfs->xAccess(pOrigVfs, zBuf, SQLITE_ACCESS_EXISTS, &exists);
   271         -      } while ( (rc==SQLITE_OK) && exists && (attempts<3) );
   272         -      if( rc==SQLITE_OK && exists ){
   273         -        rc = SQLITE_ERROR;
   274         -      }
   275         -    }
   276         -  }
   277         -
   278         -  return rc;
   279         -}
   280         -
   281    216   /* Compute the filename for the iChunk-th chunk
   282    217   */
   283    218   static int multiplexSubFilename(multiplexGroup *pGroup, int iChunk){
   284    219     if( iChunk>=pGroup->nReal ){
   285    220       struct multiplexReal *p;
   286    221       p = sqlite3_realloc(pGroup->aReal, (iChunk+1)*sizeof(*p));
   287    222       if( p==0 ){
   288    223         return SQLITE_NOMEM;
   289    224       }
   290    225       memset(&p[pGroup->nReal], 0, sizeof(p[0])*(iChunk+1-pGroup->nReal));
   291    226       pGroup->aReal = p;
   292    227       pGroup->nReal = iChunk+1;
   293    228     }
   294         -  if( pGroup->aReal[iChunk].z==0 ){
          229  +  if( pGroup->zName && pGroup->aReal[iChunk].z==0 ){
   295    230       char *z;
   296    231       int n = pGroup->nName;
   297    232       pGroup->aReal[iChunk].z = z = sqlite3_malloc( n+4 );
   298    233       if( z==0 ){
   299    234         return SQLITE_NOMEM;
   300    235       }
   301    236       memcpy(z, pGroup->zName, n+1);
................................................................................
   457    392     sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;   /* Real VFS */
   458    393     int nName;
   459    394     int sz;
   460    395     char *zToFree = 0;
   461    396   
   462    397     UNUSED_PARAMETER(pVfs);
   463    398     memset(pConn, 0, pVfs->szOsFile);
          399  +  assert( zName || (flags & SQLITE_OPEN_DELETEONCLOSE) );
   464    400   
   465    401     /* We need to create a group structure and manage
   466    402     ** access to this group of files.
   467    403     */
   468    404     multiplexEnter();
   469    405     pMultiplexOpen = (multiplexConn*)pConn;
   470    406   
   471         -  /* If the second argument to this function is NULL, generate a 
   472         -  ** temporary file name to use.  This will be handled by the
   473         -  ** original xOpen method.  We just need to allocate space for
   474         -  ** it.
   475         -  */
   476         -  if( !zName ){
   477         -    zName = zToFree = sqlite3_malloc( pOrigVfs->mxPathname + 10 );
   478         -    if( zName==0 ){
   479         -      rc = SQLITE_NOMEM;
   480         -    }else{
   481         -      rc = multiplexGetTempname(pOrigVfs, pOrigVfs->mxPathname, zToFree);
   482         -    }
   483         -  }
   484         -
   485    407     if( rc==SQLITE_OK ){
   486    408       /* allocate space for group */
   487         -    nName = multiplexStrlen30(zName);
          409  +    nName = zName ? multiplexStrlen30(zName) : 0;
   488    410       sz = sizeof(multiplexGroup)                             /* multiplexGroup */
   489    411          + nName + 1;                                         /* zName */
   490    412       pGroup = sqlite3_malloc( sz );
   491    413       if( pGroup==0 ){
   492    414         rc = SQLITE_NOMEM;
   493    415       }
   494    416     }
   495    417   
   496    418     if( rc==SQLITE_OK ){
   497    419       /* assign pointers to extra space allocated */
   498         -    char *p = (char *)&pGroup[1];
   499         -    pMultiplexOpen->pGroup = pGroup;
   500    420       memset(pGroup, 0, sz);
          421  +    pMultiplexOpen->pGroup = pGroup;
   501    422       pGroup->bEnabled = -1;
   502    423       pGroup->szChunk = SQLITE_MULTIPLEX_CHUNK_SIZE;
   503         -    if( flags & SQLITE_OPEN_URI ){
   504         -      const char *zChunkSize;
   505         -      zChunkSize = sqlite3_uri_parameter(zName, "chunksize");
   506         -      if( zChunkSize ){
   507         -        unsigned int n = 0;
   508         -        int i;
   509         -        for(i=0; zChunkSize[i]>='0' && zChunkSize[i]<='9'; i++){
   510         -          n = n*10 + zChunkSize[i] - '0';
   511         -        }
   512         -        if( n>0 ){
   513         -          pGroup->szChunk = (n+0xffff)&~0xffff;
   514         -        }else{
   515         -          /* A zero or negative chunksize disabled the multiplexor */
   516         -          pGroup->bEnabled = 0;
          424  +
          425  +    if( zName ){
          426  +      char *p = (char *)&pGroup[1];
          427  +      if( flags & SQLITE_OPEN_URI ){
          428  +        const char *zChunkSize;
          429  +        zChunkSize = sqlite3_uri_parameter(zName, "chunksize");
          430  +        if( zChunkSize ){
          431  +          unsigned int n = 0;
          432  +          int i;
          433  +          for(i=0; zChunkSize[i]>='0' && zChunkSize[i]<='9'; i++){
          434  +            n = n*10 + zChunkSize[i] - '0';
          435  +          }
          436  +          if( n>0 ){
          437  +            pGroup->szChunk = (n+0xffff)&~0xffff;
          438  +          }else{
          439  +            /* A zero or negative chunksize disabled the multiplexor */
          440  +            pGroup->bEnabled = 0;
          441  +          }
   517    442           }
   518    443         }
          444  +      pGroup->zName = p;
          445  +      memcpy(pGroup->zName, zName, nName+1);
          446  +      pGroup->nName = nName;
   519    447       }
   520         -    pGroup->zName = p;
   521         -    /* save off base filename, name length, and original open flags  */
   522         -    memcpy(pGroup->zName, zName, nName+1);
   523         -    pGroup->nName = nName;
   524    448       pGroup->flags = flags;
   525    449       rc = multiplexSubFilename(pGroup, 1);
   526    450       if( rc==SQLITE_OK ){
   527    451         pSubOpen = multiplexSubOpen(pGroup, 0, &rc, pOutFlags);
   528    452       }
   529    453       if( pSubOpen ){
   530    454         int exists, rc2, rc3;
................................................................................
   792    716   /* Pass xFileSize requests through to the original VFS.
   793    717   ** Aggregate the size of all the chunks before returning.
   794    718   */
   795    719   static int multiplexFileSize(sqlite3_file *pConn, sqlite3_int64 *pSize){
   796    720     multiplexConn *p = (multiplexConn*)pConn;
   797    721     multiplexGroup *pGroup = p->pGroup;
   798    722     int rc = SQLITE_OK;
   799         -  int rc2;
   800    723     int i;
   801    724     multiplexEnter();
   802    725     if( !pGroup->bEnabled ){
   803    726       sqlite3_file *pSubOpen = multiplexSubOpen(pGroup, 0, &rc, NULL);
   804    727       if( pSubOpen==0 ){
   805    728         rc = SQLITE_IOERR_FSTAT;
   806    729       }else{
   807    730         rc = pSubOpen->pMethods->xFileSize(pSubOpen, pSize);
   808    731       }
   809    732     }else{
   810    733       sqlite3_vfs *pOrigVfs = gMultiplex.pOrigVfs;
   811    734       *pSize = 0;
   812         -    for(i=0; 1; i++){
          735  +    for(i=0; rc==SQLITE_OK; i++){
   813    736         sqlite3_file *pSubOpen = 0;
   814    737         int exists = 0;
   815    738         rc = multiplexSubFilename(pGroup, i);
   816         -      if( rc ) break;
   817         -      if( pGroup->flags & SQLITE_OPEN_DELETEONCLOSE ){
   818         -        exists = pGroup->nReal>=i && pGroup->aReal[i].p!=0;
   819         -        rc2 = SQLITE_OK;
   820         -      }else{
   821         -        rc2 = pOrigVfs->xAccess(pOrigVfs, pGroup->aReal[i].z,
   822         -            SQLITE_ACCESS_EXISTS, &exists);
          739  +      if( rc!=SQLITE_OK ) break;
          740  +      if( pGroup->nReal>i && pGroup->aReal[i].p!=0 ){
          741  +        exists = 1;
          742  +      }else if( (pGroup->flags & SQLITE_OPEN_DELETEONCLOSE)==0 ){
          743  +        const char *zReal = pGroup->aReal[i].z;
          744  +        rc = pOrigVfs->xAccess(pOrigVfs, zReal, SQLITE_ACCESS_EXISTS, &exists);
   823    745         }
   824         -      if( rc2==SQLITE_OK && exists){
   825         -        /* if it exists, open it */
   826         -        pSubOpen = multiplexSubOpen(pGroup, i, &rc, NULL);
   827         -      }else{
   828         -        /* stop at first "gap" */
          746  +      if( exists==0 ){
          747  +        /* stop at first "gap" or IO error. */
   829    748           break;
   830    749         }
   831         -      if( pSubOpen ){
   832         -        sqlite3_int64 sz;
   833         -        rc2 = pSubOpen->pMethods->xFileSize(pSubOpen, &sz);
   834         -        if( rc2!=SQLITE_OK ){
   835         -          rc = rc2;
   836         -        }else{
   837         -          if( sz>pGroup->szChunk ){
   838         -            rc = SQLITE_IOERR_FSTAT;
   839         -          }
   840         -          *pSize += sz;
          750  +      if( rc==SQLITE_OK ){
          751  +        pSubOpen = multiplexSubOpen(pGroup, i, &rc, NULL);
          752  +      }
          753  +      assert( pSubOpen || rc!=SQLITE_OK );
          754  +      if( rc==SQLITE_OK ){
          755  +        sqlite3_int64 sz = 0;
          756  +        rc = pSubOpen->pMethods->xFileSize(pSubOpen, &sz);
          757  +        if( rc==SQLITE_OK && sz>pGroup->szChunk ){
          758  +          rc = SQLITE_IOERR_FSTAT;
   841    759           }
   842         -      }else{
   843         -        break;
          760  +        *pSize += sz;
   844    761         }
   845    762       }
   846    763     }
   847    764     multiplexLeave();
   848    765     return rc;
   849    766   }
   850    767   
................................................................................
  1187   1104     UNUSED_PARAMETER(objv);
  1188   1105   
  1189   1106     pResult = Tcl_NewObj();
  1190   1107     multiplexEnter();
  1191   1108     for(pGroup=gMultiplex.pGroups; pGroup; pGroup=pGroup->pNext){
  1192   1109       pGroupTerm = Tcl_NewObj();
  1193   1110   
  1194         -    pGroup->zName[pGroup->nName] = '\0';
  1195         -    Tcl_ListObjAppendElement(interp, pGroupTerm,
         1111  +    if( pGroup->zName ){
         1112  +      pGroup->zName[pGroup->nName] = '\0';
         1113  +      Tcl_ListObjAppendElement(interp, pGroupTerm,
  1196   1114             Tcl_NewStringObj(pGroup->zName, -1));
         1115  +    }else{
         1116  +      Tcl_ListObjAppendElement(interp, pGroupTerm, Tcl_NewObj());
         1117  +    }
  1197   1118       Tcl_ListObjAppendElement(interp, pGroupTerm,
  1198   1119             Tcl_NewIntObj(pGroup->nName));
  1199   1120       Tcl_ListObjAppendElement(interp, pGroupTerm,
  1200   1121             Tcl_NewIntObj(pGroup->flags));
  1201   1122   
  1202   1123       /* count number of chunks with open handles */
  1203   1124       for(i=0; i<pGroup->nReal; i++){

Changes to test/multiplex.test.

     9      9   #
    10     10   #***********************************************************************
    11     11   #
    12     12   
    13     13   set testdir [file dirname $argv0]
    14     14   source $testdir/tester.tcl
    15     15   source $testdir/malloc_common.tcl
           16  +
           17  +# The tests in this file assume that SQLite is compiled without
           18  +# ENABLE_8_3_NAMES.
           19  +#
           20  +ifcapable 8_3_names {
           21  +  puts -nonewline "SQLite compiled with SQLITE_ENABLE_8_3_NAMES. "
           22  +  puts            "Skipping tests multiplex-*."
           23  +  finish_test
           24  +  return
           25  +}
    16     26   
    17     27   set g_chunk_size [ expr ($::SQLITE_MAX_PAGE_SIZE*16384) ]
    18     28   set g_max_chunks 32
    19     29   
    20     30   # This handles appending the chunk number
    21     31   # to the end of the filename.  if 
    22     32   # SQLITE_MULTIPLEX_EXT_OVWR is defined, then

Added test/multiplex3.test.

            1  +
            2  +# 2011 December 13
            3  +#
            4  +# The author disclaims copyright to this source code.  In place of
            5  +# a legal notice, here is a blessing:
            6  +#
            7  +#    May you do good and not evil.
            8  +#    May you find forgiveness for yourself and forgive others.
            9  +#    May you share freely, never taking more than you give.
           10  +#
           11  +#***********************************************************************
           12  +#
           13  +# This file contains tests for error (IO, OOM etc.) handling when using
           14  +# the multiplexor extension with 8.3 filenames.
           15  +#
           16  +
           17  +set testdir [file dirname $argv0]
           18  +source $testdir/tester.tcl
           19  +source $testdir/malloc_common.tcl
           20  +set ::testprefix multiplex3
           21  +
           22  +ifcapable !8_3_names {
           23  +  puts -nonewline "SQLite compiled without SQLITE_ENABLE_8_3_NAMES. "
           24  +  puts            "Skipping tests multiplex3-*."
           25  +  finish_test
           26  +  return
           27  +}
           28  +
           29  +db close
           30  +sqlite3_shutdown
           31  +sqlite3_config_uri 1
           32  +autoinstall_test_functions
           33  +
           34  +sqlite3_multiplex_initialize "" 1
           35  +
           36  +proc destroy_vfs_stack {} {
           37  +  generic_unregister stack
           38  +  sqlite3_multiplex_shutdown
           39  +}
           40  +
           41  +proc multiplex_delete_db {} {
           42  +  forcedelete test.db
           43  +  for {set i 1} {$i <= 1000} {incr i} {
           44  +    forcedelete test.[format %03d $i]
           45  +  }
           46  +}
           47  +
           48  +# Procs to save and restore the current muliplexed database.
           49  +#
           50  +proc multiplex_save_db {} {
           51  +  foreach f [glob -nocomplain sv_test.*] { forcedelete $f }
           52  +  foreach f [glob -nocomplain test.*]    { forcecopy $f "sv_$f" }
           53  +}
           54  +proc multiplex_restore_db {} {
           55  +  foreach f [glob -nocomplain test.*]    {forcedelete $f}
           56  +  foreach f [glob -nocomplain sv_test.*] {forcecopy $f [string range $f 3 end]} }
           57  +
           58  +
           59  +do_test 1.0 {
           60  +  multiplex_delete_db
           61  +  sqlite3 db file:test.db?8_3_names=1
           62  +  sqlite3_multiplex_control db main chunk_size [expr 256*1024]
           63  +  execsql {
           64  +    CREATE TABLE t1(a PRIMARY KEY, b);
           65  +    INSERT INTO t1 VALUES(randomblob(15), randomblob(2000));
           66  +    INSERT INTO t1 SELECT randomblob(15), randomblob(2000) FROM t1;    --   2
           67  +    INSERT INTO t1 SELECT randomblob(15), randomblob(2000) FROM t1;    --   4
           68  +    INSERT INTO t1 SELECT randomblob(15), randomblob(2000) FROM t1;    --   8
           69  +    INSERT INTO t1 SELECT randomblob(15), randomblob(2000) FROM t1;    --  16
           70  +    INSERT INTO t1 SELECT randomblob(15), randomblob(2000) FROM t1;    --  32
           71  +    INSERT INTO t1 SELECT randomblob(15), randomblob(2000) FROM t1;    --  64
           72  +    INSERT INTO t1 SELECT randomblob(15), randomblob(2000) FROM t1;    -- 128
           73  +    INSERT INTO t1 SELECT randomblob(15), randomblob(2000) FROM t1;    -- 256
           74  +    INSERT INTO t1 SELECT randomblob(15), randomblob(2000) FROM t1;    -- 512
           75  +  }
           76  +  set ::cksum1 [execsql {SELECT md5sum(a, b) FROM t1 ORDER BY a}]
           77  +  db close
           78  +  multiplex_save_db
           79  +} {}
           80  +
           81  +do_faultsim_test 1 -prep {
           82  +  multiplex_restore_db
           83  +  sqlite3 db file:test.db?8_3_names=1
           84  +  sqlite3_multiplex_control db main chunk_size [expr 256*1024]
           85  +} -body {
           86  +  execsql "UPDATE t1 SET a=randomblob(12), b=randomblob(1500) WHERE (rowid%32)=0"
           87  +} -test {
           88  +  faultsim_test_result {0 {}}
           89  +  if {$testrc!=0} {
           90  +    set cksum2 [execsql {SELECT md5sum(a, b) FROM t1 ORDER BY a}]
           91  +    if {$cksum2 != $::cksum1} { error "data mismatch" }
           92  +  }
           93  +}
           94  +
           95  +catch { db close }
           96  +
           97  +sqlite3_multiplex_shutdown
           98  +finish_test