Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch snapshot-revert Excluding Merge-Ins
This is equivalent to a diff from 7e38c889e7 to 00af3bd254
2021-03-26
| ||
13:31 | Merge from 3.35.3 into the begin-concurrent branch. (check-in: 988da36c86 user: drh tags: begin-concurrent) | |
2021-03-24
| ||
19:57 | Add a comment to snapshotrevert.c warning of the undefined effects reverting to an old snapshot may have on newer readers. (Leaf check-in: 00af3bd254 user: dan tags: snapshot-revert) | |
19:54 | Add experimental extension "snapshotrevert". (check-in: 2d5ee3bad1 user: dan tags: snapshot-revert) | |
2021-03-12
| ||
16:18 | Merge version 3.35.0 changes into the begin-concurrent branch. (check-in: 7e38c889e7 user: drh tags: begin-concurrent) | |
15:10 | Version 3.35.0 (check-in: acd63062eb user: drh tags: trunk, release, major-release, version-3.35.0) | |
2021-03-11
| ||
18:52 | Update this branch with latest changes from trunk. (check-in: 79d44ebd53 user: dan tags: begin-concurrent) | |
Changes to Makefile.in.
︙ | ︙ | |||
458 459 460 461 462 463 464 465 466 467 468 469 470 471 | $(TOP)/ext/misc/nextchar.c \ $(TOP)/ext/misc/normalize.c \ $(TOP)/ext/misc/percentile.c \ $(TOP)/ext/misc/prefixes.c \ $(TOP)/ext/misc/regexp.c \ $(TOP)/ext/misc/remember.c \ $(TOP)/ext/misc/series.c \ $(TOP)/ext/misc/spellfix.c \ $(TOP)/ext/misc/totype.c \ $(TOP)/ext/misc/unionvtab.c \ $(TOP)/ext/misc/wholenumber.c \ $(TOP)/ext/misc/zipfile.c \ $(TOP)/ext/userauth/userauth.c | > | 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 | $(TOP)/ext/misc/nextchar.c \ $(TOP)/ext/misc/normalize.c \ $(TOP)/ext/misc/percentile.c \ $(TOP)/ext/misc/prefixes.c \ $(TOP)/ext/misc/regexp.c \ $(TOP)/ext/misc/remember.c \ $(TOP)/ext/misc/series.c \ $(TOP)/ext/misc/snapshotrevert.c \ $(TOP)/ext/misc/spellfix.c \ $(TOP)/ext/misc/totype.c \ $(TOP)/ext/misc/unionvtab.c \ $(TOP)/ext/misc/wholenumber.c \ $(TOP)/ext/misc/zipfile.c \ $(TOP)/ext/userauth/userauth.c |
︙ | ︙ |
Changes to Makefile.msc.
︙ | ︙ | |||
1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 | $(TOP)\ext\misc\nextchar.c \ $(TOP)\ext\misc\normalize.c \ $(TOP)\ext\misc\percentile.c \ $(TOP)\ext\misc\prefixes.c \ $(TOP)\ext\misc\regexp.c \ $(TOP)\ext\misc\remember.c \ $(TOP)\ext\misc\series.c \ $(TOP)\ext\misc\spellfix.c \ $(TOP)\ext\misc\totype.c \ $(TOP)\ext\misc\unionvtab.c \ $(TOP)\ext\misc\wholenumber.c # If use of zlib is enabled, add the "zipfile.c" source file. # | > | 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 | $(TOP)\ext\misc\nextchar.c \ $(TOP)\ext\misc\normalize.c \ $(TOP)\ext\misc\percentile.c \ $(TOP)\ext\misc\prefixes.c \ $(TOP)\ext\misc\regexp.c \ $(TOP)\ext\misc\remember.c \ $(TOP)\ext\misc\series.c \ $(TOP)\ext\misc\snapshotrevert.c \ $(TOP)\ext\misc\spellfix.c \ $(TOP)\ext\misc\totype.c \ $(TOP)\ext\misc\unionvtab.c \ $(TOP)\ext\misc\wholenumber.c # If use of zlib is enabled, add the "zipfile.c" source file. # |
︙ | ︙ |
Added ext/misc/snapshotrevert.c.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | /* ** 2021 March 25 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** ** Code to revert a database to a snapshot. The procedure for reverting a ** live database to the supplied snapshot is: ** ** 1. Open snapshot for reading. ** 2. Take exclusive CHECKPOINTER lock. ** 3. Take exclusive WRITER lock. ** 4. Clobber the current wal-index header with the snapshot. ** 5. Set nBackfill to 0. nBackfillAttempted is not modified. ** 6. Truncate wal file. ** 7. Release write lock. ** 8. Release checkpoint lock. ** 9. Close snapshot transaction. ** ** This extension exports a single API function: ** ** int sqlite3_snapshot_revert( ** sqlite3 *db, ** const char *zDb, ** sqlite3_snapshot *pSnap ** ); ** ** See comments above the implementation of this function below for details. */ #include <sqlite3.h> #include <string.h> #include <assert.h> #if !defined(SQLITE_TEST) || defined(SQLITE_ENABLE_SNAPSHOT) /* ** Values for the eLock parameter accepted by snapshotRevertLock() and ** snapshotRevertUnlock(). */ #define SNAPSHOT_REVERT_CHECKPOINTER 2 #define SNAPSHOT_REVERT_WRITER 0 static int snapshotRevertLock(sqlite3_file *pFd, int eLock){ int f = SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE; return pFd->pMethods->xShmLock(pFd, eLock, 1, f); } static int snapshotRevertUnlock(sqlite3_file *pFd, int eLock){ int f = SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE; return pFd->pMethods->xShmLock(pFd, eLock, 1, f); } /* ** Revert database zDb of connection db to the state it was in when snapshot ** pSnap was taken. The database handle must be in auto-commit mode and ** not have an open read or write transction on zDb when this function is ** called. ** ** This function uses normal SQLite locks to ensure that the database is ** not corrupted by a simultaneous writer or checkpointer. However, the ** effects of a successful call to this function on readers that are ** reading from a snapshot newer than the snapshot supplied as the ** third argument are undefined. ** ** Return SQLITE_OK if successful, or an SQLite error code otherwise. */ int sqlite3_snapshot_revert( sqlite3 *db, const char *zDb, sqlite3_snapshot *pSnap ){ sqlite3_file *pDbFd = 0; sqlite3_file *pWalFd = 0; int rc; volatile void *pShm = 0; sqlite3_stmt *pCommit = 0; int nLock = 0; /* Successful snapshotRevertLock() calls */ /* Put the db handle in non-auto-commit mode, as required by the ** sqlite3_snapshot_open() API. ** ** Also prepare a "COMMIT" command to end the transaction. Such a VM does not ** need to allocate memory or do anything else that is likely to fail, ** so we ignore the error code when it is eventually executed and assume ** that the transaction was successfully closed. */ rc = sqlite3_prepare_v2(db, "COMMIT", -1, &pCommit, 0); if( rc==SQLITE_OK ){ rc = sqlite3_exec(db, "BEGIN", 0, 0, 0); } if( rc!=SQLITE_OK ){ sqlite3_finalize(pCommit); return rc; } /* 1. Open snapshot for reading */ rc = sqlite3_snapshot_open(db, zDb, pSnap); /* Obtain pointers to the database file handle, the shared-memory mapping, ** and the wal file handle. */ if( rc==SQLITE_OK ){ const int op = SQLITE_FCNTL_FILE_POINTER; rc = sqlite3_file_control(db, zDb, op, (void*)&pDbFd); } if( rc==SQLITE_OK ){ rc = pDbFd->pMethods->xShmMap(pDbFd, 0, 32*1024, 1, &pShm); } if( rc==SQLITE_OK ){ const int op = SQLITE_FCNTL_JOURNAL_POINTER; rc = sqlite3_file_control(db, zDb, op, (void*)&pWalFd); } /* 2. Take exclusive CHECKPOINTER lock */ if( rc==SQLITE_OK ){ rc = snapshotRevertLock(pDbFd, SNAPSHOT_REVERT_CHECKPOINTER); if( rc==SQLITE_OK ) nLock = 1; } /* 3. Take exclusive WRITER lock */ if( rc==SQLITE_OK ){ rc = snapshotRevertLock(pDbFd, SNAPSHOT_REVERT_WRITER); if( rc==SQLITE_OK ) nLock = 2; } if( rc==SQLITE_OK ){ /* Constants from https://www.sqlite.org/walformat.html#walidxfmt */ const int nWalHdrSz = 32; /* Size of wal file header */ const int nIdxHdrSz = 48; /* Size of each WalIndexHdr */ const int nFrameHdrSz = 24; /* Size of each frame header */ const int iHdrOff1 = 0; /* Offset of first WalIndexHdr */ const int iHdrOff2 = 48; /* Offset of second WalIndexHdr */ const int iBackfillOff = 96; /* offset of 32-bit nBackfill value */ const int iPgszOff = 14; /* Offset of 16-bit page-size value */ const int iMxFrameOff = 16; /* Offset of 32-bit mxFrame value */ unsigned char *a = (unsigned char*)pShm; int pgsz; /* Database page size */ int mxFrame; /* Valid frames in wal file after revert */ sqlite3_int64 szWal; /* Size in bytes to truncate wal file to */ /* 4. Clobber the current wal-index header with the snapshot. */ memcpy(&a[iHdrOff1], pSnap, nIdxHdrSz); memcpy(&a[iHdrOff2], pSnap, nIdxHdrSz); /* 5. Set nBackfill to 0. nBackfillAttempted is not modified. */ *(int*)&a[iBackfillOff] = 0; /* 6. Truncate the wal file */ assert( sizeof(unsigned short int)==2 ); pgsz = *(unsigned short int*)&a[iPgszOff]; if( pgsz==1 ) pgsz = 65536; mxFrame = *(int*)&a[iMxFrameOff]; szWal = (sqlite3_int64)mxFrame * (pgsz + nFrameHdrSz) + nWalHdrSz; rc = pWalFd->pMethods->xTruncate(pWalFd, szWal); } /* Steps 8 and 9 - drop locks if they were acquired */ if( nLock==2 ) snapshotRevertUnlock(pDbFd, SNAPSHOT_REVERT_WRITER); if( nLock>0 ) snapshotRevertUnlock(pDbFd, SNAPSHOT_REVERT_CHECKPOINTER); /* End the snapshot transaction, if one was opened. */ sqlite3_step(pCommit); sqlite3_finalize(pCommit); return rc; } #endif /* !defined(SQLITE_TEST) || defined(SQLITE_ENABLE_SNAPSHOT) */ |
Changes to main.mk.
︙ | ︙ | |||
375 376 377 378 379 380 381 382 383 384 385 386 387 388 | $(TOP)/ext/misc/nextchar.c \ $(TOP)/ext/misc/normalize.c \ $(TOP)/ext/misc/percentile.c \ $(TOP)/ext/misc/prefixes.c \ $(TOP)/ext/misc/regexp.c \ $(TOP)/ext/misc/remember.c \ $(TOP)/ext/misc/series.c \ $(TOP)/ext/misc/spellfix.c \ $(TOP)/ext/misc/totype.c \ $(TOP)/ext/misc/unionvtab.c \ $(TOP)/ext/misc/wholenumber.c \ $(TOP)/ext/misc/zipfile.c \ $(TOP)/ext/fts5/fts5_tcl.c \ $(TOP)/ext/fts5/fts5_test_mi.c \ | > | 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 | $(TOP)/ext/misc/nextchar.c \ $(TOP)/ext/misc/normalize.c \ $(TOP)/ext/misc/percentile.c \ $(TOP)/ext/misc/prefixes.c \ $(TOP)/ext/misc/regexp.c \ $(TOP)/ext/misc/remember.c \ $(TOP)/ext/misc/series.c \ $(TOP)/ext/misc/snapshotrevert.c \ $(TOP)/ext/misc/spellfix.c \ $(TOP)/ext/misc/totype.c \ $(TOP)/ext/misc/unionvtab.c \ $(TOP)/ext/misc/wholenumber.c \ $(TOP)/ext/misc/zipfile.c \ $(TOP)/ext/fts5/fts5_tcl.c \ $(TOP)/ext/fts5/fts5_test_mi.c \ |
︙ | ︙ |
Changes to src/test1.c.
︙ | ︙ | |||
2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 | res = sqlite3_snapshot_cmp((sqlite3_snapshot*)p1, (sqlite3_snapshot*)p2); Tcl_SetObjResult(interp, Tcl_NewIntObj(res)); return TCL_OK; } #endif /* SQLITE_ENABLE_SNAPSHOT */ /* ** Usage: sqlite3_delete_database FILENAME */ int sqlite3_delete_database(const char*); /* in test_delete.c */ static int SQLITE_TCLAPI test_delete_database( void * clientData, Tcl_Interp *interp, | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 | res = sqlite3_snapshot_cmp((sqlite3_snapshot*)p1, (sqlite3_snapshot*)p2); Tcl_SetObjResult(interp, Tcl_NewIntObj(res)); return TCL_OK; } #endif /* SQLITE_ENABLE_SNAPSHOT */ #ifdef SQLITE_ENABLE_SNAPSHOT /* ** Usage: sqlite3_snapshot_revert DB DBNAME SNAPSHOT */ static int SQLITE_TCLAPI test_snapshot_revert( void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ extern int sqlite3_snapshot_revert(sqlite3*, const char*, sqlite3_snapshot*); int rc; sqlite3 *db; char *zName; sqlite3_snapshot *pSnapshot; if( objc!=4 ){ Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME SNAPSHOT"); return TCL_ERROR; } if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; zName = Tcl_GetString(objv[2]); pSnapshot = (sqlite3_snapshot*)sqlite3TestTextToPtr(Tcl_GetString(objv[3])); rc = sqlite3_snapshot_revert(db, zName, pSnapshot); if( rc!=SQLITE_OK ){ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1)); return TCL_ERROR; }else{ Tcl_ResetResult(interp); } return TCL_OK; } #endif /* ** Usage: sqlite3_delete_database FILENAME */ int sqlite3_delete_database(const char*); /* in test_delete.c */ static int SQLITE_TCLAPI test_delete_database( void * clientData, Tcl_Interp *interp, |
︙ | ︙ | |||
8521 8522 8523 8524 8525 8526 8527 8528 8529 8530 8531 8532 8533 8534 | { "sqlite3_snapshot_open", test_snapshot_open, 0 }, { "sqlite3_snapshot_free", test_snapshot_free, 0 }, { "sqlite3_snapshot_cmp", test_snapshot_cmp, 0 }, { "sqlite3_snapshot_recover", test_snapshot_recover, 0 }, { "sqlite3_snapshot_get_blob", test_snapshot_get_blob, 0 }, { "sqlite3_snapshot_open_blob", test_snapshot_open_blob, 0 }, { "sqlite3_snapshot_cmp_blob", test_snapshot_cmp_blob, 0 }, #endif { "sqlite3_delete_database", test_delete_database, 0 }, { "sqlite3_wal_info", test_wal_info, 0 }, { "atomic_batch_write", test_atomic_batch_write, 0 }, { "sqlite3_mmap_warm", test_mmap_warm, 0 }, { "sqlite3_config_sorterref", test_config_sorterref, 0 }, { "decode_hexdb", test_decode_hexdb, 0 }, | > | 8556 8557 8558 8559 8560 8561 8562 8563 8564 8565 8566 8567 8568 8569 8570 | { "sqlite3_snapshot_open", test_snapshot_open, 0 }, { "sqlite3_snapshot_free", test_snapshot_free, 0 }, { "sqlite3_snapshot_cmp", test_snapshot_cmp, 0 }, { "sqlite3_snapshot_recover", test_snapshot_recover, 0 }, { "sqlite3_snapshot_get_blob", test_snapshot_get_blob, 0 }, { "sqlite3_snapshot_open_blob", test_snapshot_open_blob, 0 }, { "sqlite3_snapshot_cmp_blob", test_snapshot_cmp_blob, 0 }, { "sqlite3_snapshot_revert", test_snapshot_revert, 0 }, #endif { "sqlite3_delete_database", test_delete_database, 0 }, { "sqlite3_wal_info", test_wal_info, 0 }, { "atomic_batch_write", test_atomic_batch_write, 0 }, { "sqlite3_mmap_warm", test_mmap_warm, 0 }, { "sqlite3_config_sorterref", test_config_sorterref, 0 }, { "decode_hexdb", test_decode_hexdb, 0 }, |
︙ | ︙ |
Added test/snapshot_revert.test.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | # 2021 March 25 # # The author disclaims copyright to this source code. In place of # a legal notice, here is a blessing: # # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** # # Tests for the sqlite3_snapshot_revert() extension. # set testdir [file dirname $argv0] source $testdir/tester.tcl ifcapable !snapshot {finish_test; return} set testprefix snapshot_revert do_execsql_test 1.0 { CREATE TABLE t1(a, b, c); PRAGMA journal_mode = wal; INSERT INTO t1 VALUES(1, 2, 3); INSERT INTO t1 VALUES(4, 5, 6); INSERT INTO t1 VALUES(7, 8, 9); } {wal} do_test 1.1 { execsql BEGIN set ::snap1 [sqlite3_snapshot_get db main] execsql COMMIT } {} do_execsql_test 1.2 { INSERT INTO t1 VALUES(10, 11, 12); INSERT INTO t1 VALUES(13, 14, 15); } sqlite3 db2 test.db do_execsql_test -db db2 1.3 { SELECT * FROM t1 } {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15} do_test 1.4 { sqlite3_snapshot_revert db main $::snap1 } {} sqlite3_snapshot_free $::snap1 do_execsql_test -db db2 1.5 { SELECT * FROM t1 } {1 2 3 4 5 6 7 8 9} db2 close db_save_and_close db_restore_and_reopen do_execsql_test 1.6 { SELECT * FROM t1 } {1 2 3 4 5 6 7 8 9} #------------------------------------------------------------------------- # Test some error conditions: # # 2.1.* Error - already in non-auto-commit mode. # 2.2.* Error - snapshot has already been checkpointed away. # 2.3.* Error - cannot get WRITER lock. # 2.4.* Check that CHECKPOINTER is released if cannot get WRITER # reset_db do_execsql_test 2.0.1 { PRAGMA auto_vacuum = 0; PRAGMA journal_mode = wal; CREATE TABLE x1(x, y); INSERT INTO x1 VALUES('A', 'B'), ('C', 'D'); } {wal} do_test 2.0.2 { execsql BEGIN set ::snap1 [sqlite3_snapshot_get db main] execsql COMMIT } {} do_test 2.1.0 { execsql BEGIN list [catch { sqlite3_snapshot_revert db main $::snap1 } msg] $msg } {1 SQLITE_ERROR} do_test 2.1.1 { execsql COMMIT list [catch { sqlite3_snapshot_revert db main $::snap1 } msg] $msg } {0 {}} do_test 2.2.0 { execsql { INSERT INTO x1 VALUES('E', 'F'); DELETE FROM x1 WHERE x='A'; PRAGMA wal_checkpoint; } list [catch { sqlite3_snapshot_revert db main $::snap1 } msg] $msg } {1 SQLITE_ERROR_SNAPSHOT} sqlite3_snapshot_free $::snap1 sqlite3 db2 test.db do_test 2.3.0 { execsql { INSERT INTO x1 VALUES('G', 'H'); BEGIN; } set ::snap1 [sqlite3_snapshot_get db main] execsql { DELETE FROM x1 WHERE x='C'; COMMIT; } } {} do_test 2.3.1 { execsql { BEGIN EXCLUSIVE } db2 list [catch { sqlite3_snapshot_revert db main $::snap1 } msg] $msg } {1 SQLITE_BUSY} do_execsql_test 2.3.2 { SELECT * FROM x1 } {E F G H} do_test 2.3.3 { execsql { COMMIT } db2 list [catch { sqlite3_snapshot_revert db main $::snap1 } msg] $msg } {0 {}} do_execsql_test 2.3.4 { SELECT * FROM x1 } {C D E F G H} sqlite3_snapshot_free $::snap1 do_test 2.4.0 { execsql { BEGIN; } set ::snap1 [sqlite3_snapshot_get db main] execsql { INSERT INTO x1 VALUES('I', 'J'); DELETE FROM x1 WHERE x IN ('C', 'E'); COMMIT; } } {} do_execsql_test -db db2 2.4.1 { BEGIN EXCLUSIVE; SELECT * FROM x1; } {G H I J} do_test 2.4.2 { list [catch { sqlite3_snapshot_revert db main $::snap1 } msg] $msg } {1 SQLITE_BUSY} do_execsql_test -db db2 2.4.3 { COMMIT; PRAGMA wal_checkpoint; } {0 2 2} sqlite3_snapshot_free $::snap1 db close db2 close finish_test |