/ Check-in [b70c85ce]
Login

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

Overview
Comment:Add the sqlite3_snapshot_recover() interface and related functionality.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: b70c85ce6d07b08e13f4ea99edb8f4855a6d7580
User & Date: drh 2016-11-26 19:44:40
Context
2016-11-28
14:52
Simplify some code in fts5_expr.c. Add some extra test cases for the same. check-in: 7cc2d60d user: dan tags: trunk
2016-11-26
20:44
Merge all recent trunk changes, and especially the new sqlite3_snapshot_recover() interface. check-in: 41a3af54 user: drh tags: apple-osx
19:44
Add the sqlite3_snapshot_recover() interface and related functionality. check-in: b70c85ce user: drh tags: trunk
2016-11-25
20:20
Clarification of the behavior of sqlite3_stmt_readonly() on BEGIN IMMEDIATE statements. The is comment changes only - no changes to code. check-in: a4205a83 user: drh tags: trunk
2016-11-22
22:23
Merge bug fixes and enhancements from trunk. Closed-Leaf check-in: e2c4bdf5 user: drh tags: serializable-snapshot
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/main.c.

3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992

3993
3994
3995
3996
3997
3998
3999

4000
4001
4002
4003
4004
4005
4006
....
4034
4035
4036
4037
4038
4039
4040
































4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
int sqlite3_snapshot_get(
  sqlite3 *db, 
  const char *zDb,
  sqlite3_snapshot **ppSnapshot
){
  int rc = SQLITE_ERROR;
#ifndef SQLITE_OMIT_WAL
  int iDb;

#ifdef SQLITE_ENABLE_API_ARMOR
  if( !sqlite3SafetyCheckOk(db) ){
    return SQLITE_MISUSE_BKPT;
  }
#endif
  sqlite3_mutex_enter(db->mutex);


  iDb = sqlite3FindDbName(db, zDb);
  if( iDb==0 || iDb>1 ){
    Btree *pBt = db->aDb[iDb].pBt;
    if( 0==sqlite3BtreeIsInTrans(pBt) ){
      rc = sqlite3BtreeBeginTrans(pBt, 0);
      if( rc==SQLITE_OK ){
        rc = sqlite3PagerSnapshotGet(sqlite3BtreePager(pBt), ppSnapshot);

      }
    }
  }

  sqlite3_mutex_leave(db->mutex);
#endif   /* SQLITE_OMIT_WAL */
  return rc;
................................................................................
          rc = sqlite3BtreeBeginTrans(pBt, 0);
          sqlite3PagerSnapshotOpen(sqlite3BtreePager(pBt), 0);
        }
      }
    }
  }

































  sqlite3_mutex_leave(db->mutex);
#endif   /* SQLITE_OMIT_WAL */
  return rc;
}

/*
** Free a snapshot handle obtained from sqlite3_snapshot_get().
*/
void sqlite3_snapshot_free(sqlite3_snapshot *pSnapshot){
  sqlite3_free(pSnapshot);
}
#endif /* SQLITE_ENABLE_SNAPSHOT */







<








>
|
|
|
|
|
|
|
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>












3977
3978
3979
3980
3981
3982
3983

3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004
4005
4006
4007
....
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
int sqlite3_snapshot_get(
  sqlite3 *db, 
  const char *zDb,
  sqlite3_snapshot **ppSnapshot
){
  int rc = SQLITE_ERROR;
#ifndef SQLITE_OMIT_WAL


#ifdef SQLITE_ENABLE_API_ARMOR
  if( !sqlite3SafetyCheckOk(db) ){
    return SQLITE_MISUSE_BKPT;
  }
#endif
  sqlite3_mutex_enter(db->mutex);

  if( db->autoCommit==0 ){
    int iDb = sqlite3FindDbName(db, zDb);
    if( iDb==0 || iDb>1 ){
      Btree *pBt = db->aDb[iDb].pBt;
      if( 0==sqlite3BtreeIsInTrans(pBt) ){
        rc = sqlite3BtreeBeginTrans(pBt, 0);
        if( rc==SQLITE_OK ){
          rc = sqlite3PagerSnapshotGet(sqlite3BtreePager(pBt), ppSnapshot);
        }
      }
    }
  }

  sqlite3_mutex_leave(db->mutex);
#endif   /* SQLITE_OMIT_WAL */
  return rc;
................................................................................
          rc = sqlite3BtreeBeginTrans(pBt, 0);
          sqlite3PagerSnapshotOpen(sqlite3BtreePager(pBt), 0);
        }
      }
    }
  }

  sqlite3_mutex_leave(db->mutex);
#endif   /* SQLITE_OMIT_WAL */
  return rc;
}

/*
** Recover as many snapshots as possible from the wal file associated with
** schema zDb of database db.
*/
int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb){
  int rc = SQLITE_ERROR;
  int iDb;
#ifndef SQLITE_OMIT_WAL

#ifdef SQLITE_ENABLE_API_ARMOR
  if( !sqlite3SafetyCheckOk(db) ){
    return SQLITE_MISUSE_BKPT;
  }
#endif

  sqlite3_mutex_enter(db->mutex);
  iDb = sqlite3FindDbName(db, zDb);
  if( iDb==0 || iDb>1 ){
    Btree *pBt = db->aDb[iDb].pBt;
    if( 0==sqlite3BtreeIsInReadTrans(pBt) ){
      rc = sqlite3BtreeBeginTrans(pBt, 0);
      if( rc==SQLITE_OK ){
        rc = sqlite3PagerSnapshotRecover(sqlite3BtreePager(pBt));
        sqlite3BtreeCommit(pBt);
      }
    }
  }
  sqlite3_mutex_leave(db->mutex);
#endif   /* SQLITE_OMIT_WAL */
  return rc;
}

/*
** Free a snapshot handle obtained from sqlite3_snapshot_get().
*/
void sqlite3_snapshot_free(sqlite3_snapshot *pSnapshot){
  sqlite3_free(pSnapshot);
}
#endif /* SQLITE_ENABLE_SNAPSHOT */

Changes to src/pager.c.

7401
7402
7403
7404
7405
7406
7407














7408
7409
7410
7411
7412
7413
7414
  if( pPager->pWal ){
    sqlite3WalSnapshotOpen(pPager->pWal, pSnapshot);
  }else{
    rc = SQLITE_ERROR;
  }
  return rc;
}














#endif /* SQLITE_ENABLE_SNAPSHOT */
#endif /* !SQLITE_OMIT_WAL */

#ifdef SQLITE_ENABLE_ZIPVFS
/*
** A read-lock must be held on the pager when this function is called. If
** the pager is in WAL mode and the WAL file currently contains one or more







>
>
>
>
>
>
>
>
>
>
>
>
>
>







7401
7402
7403
7404
7405
7406
7407
7408
7409
7410
7411
7412
7413
7414
7415
7416
7417
7418
7419
7420
7421
7422
7423
7424
7425
7426
7427
7428
  if( pPager->pWal ){
    sqlite3WalSnapshotOpen(pPager->pWal, pSnapshot);
  }else{
    rc = SQLITE_ERROR;
  }
  return rc;
}

/*
** If this is a WAL database, call sqlite3WalSnapshotRecover(). If this 
** is not a WAL database, return an error.
*/
int sqlite3PagerSnapshotRecover(Pager *pPager){
  int rc;
  if( pPager->pWal ){
    rc = sqlite3WalSnapshotRecover(pPager->pWal);
  }else{
    rc = SQLITE_ERROR;
  }
  return rc;
}
#endif /* SQLITE_ENABLE_SNAPSHOT */
#endif /* !SQLITE_OMIT_WAL */

#ifdef SQLITE_ENABLE_ZIPVFS
/*
** A read-lock must be held on the pager when this function is called. If
** the pager is in WAL mode and the WAL file currently contains one or more

Changes to src/pager.h.

178
179
180
181
182
183
184

185
186
187
188
189
190
191
  int sqlite3PagerWalCallback(Pager *pPager);
  int sqlite3PagerOpenWal(Pager *pPager, int *pisOpen);
  int sqlite3PagerCloseWal(Pager *pPager, sqlite3*);
  int sqlite3PagerUseWal(Pager *pPager);
# ifdef SQLITE_ENABLE_SNAPSHOT
  int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot);
  int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot);

# endif
#else
# define sqlite3PagerUseWal(x) 0
#endif

#ifdef SQLITE_ENABLE_ZIPVFS
  int sqlite3PagerWalFramesize(Pager *pPager);







>







178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
  int sqlite3PagerWalCallback(Pager *pPager);
  int sqlite3PagerOpenWal(Pager *pPager, int *pisOpen);
  int sqlite3PagerCloseWal(Pager *pPager, sqlite3*);
  int sqlite3PagerUseWal(Pager *pPager);
# ifdef SQLITE_ENABLE_SNAPSHOT
  int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot);
  int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot);
  int sqlite3PagerSnapshotRecover(Pager *pPager);
# endif
#else
# define sqlite3PagerUseWal(x) 0
#endif

#ifdef SQLITE_ENABLE_ZIPVFS
  int sqlite3PagerWalFramesize(Pager *pPager);

Changes to src/sqlite.h.in.

8283
8284
8285
8286
8287
8288
8289
8290


8291
8292
8293
8294
8295
8296
8297
8298
8299
8300











8301
8302
8303














8304
8305
8306
8307
8308
8309
8310
....
8389
8390
8391
8392
8393
8394
8395






















8396
8397
8398
8399
8400
8401
8402
8403
8404
8405
8406
8407
** the most recent version.
**
** The constructor for this object is [sqlite3_snapshot_get()].  The
** [sqlite3_snapshot_open()] method causes a fresh read transaction to refer
** to an historical snapshot (if possible).  The destructor for 
** sqlite3_snapshot objects is [sqlite3_snapshot_free()].
*/
typedef struct sqlite3_snapshot sqlite3_snapshot;



/*
** CAPI3REF: Record A Database Snapshot
** EXPERIMENTAL
**
** ^The [sqlite3_snapshot_get(D,S,P)] interface attempts to make a
** new [sqlite3_snapshot] object that records the current state of
** schema S in database connection D.  ^On success, the
** [sqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly
** created [sqlite3_snapshot] object into *P and returns SQLITE_OK.











** ^If schema S of [database connection] D is not a [WAL mode] database
** that is in a read transaction, then [sqlite3_snapshot_get(D,S,P)]
** leaves the *P value unchanged and returns an appropriate [error code].














**
** The [sqlite3_snapshot] object returned from a successful call to
** [sqlite3_snapshot_get()] must be freed using [sqlite3_snapshot_free()]
** to avoid a memory leak.
**
** The [sqlite3_snapshot_get()] interface is only available when the
** SQLITE_ENABLE_SNAPSHOT compile-time option is used.
................................................................................
** snapshot, and a positive value if P1 is a newer snapshot than P2.
*/
SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
  sqlite3_snapshot *p1,
  sqlite3_snapshot *p2
);























/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.
*/
#ifdef SQLITE_OMIT_FLOATING_POINT
# undef double
#endif

#ifdef __cplusplus
}  /* End of the 'extern "C"' block */
#endif
#endif /* SQLITE3_H */







|
>
>










>
>
>
>
>
>
>
>
>
>
>
|
<
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>












8283
8284
8285
8286
8287
8288
8289
8290
8291
8292
8293
8294
8295
8296
8297
8298
8299
8300
8301
8302
8303
8304
8305
8306
8307
8308
8309
8310
8311
8312
8313
8314


8315
8316
8317
8318
8319
8320
8321
8322
8323
8324
8325
8326
8327
8328
8329
8330
8331
8332
8333
8334
8335
....
8414
8415
8416
8417
8418
8419
8420
8421
8422
8423
8424
8425
8426
8427
8428
8429
8430
8431
8432
8433
8434
8435
8436
8437
8438
8439
8440
8441
8442
8443
8444
8445
8446
8447
8448
8449
8450
8451
8452
8453
8454
** the most recent version.
**
** The constructor for this object is [sqlite3_snapshot_get()].  The
** [sqlite3_snapshot_open()] method causes a fresh read transaction to refer
** to an historical snapshot (if possible).  The destructor for 
** sqlite3_snapshot objects is [sqlite3_snapshot_free()].
*/
typedef struct sqlite3_snapshot {
  unsigned char hidden[48];
} sqlite3_snapshot;

/*
** CAPI3REF: Record A Database Snapshot
** EXPERIMENTAL
**
** ^The [sqlite3_snapshot_get(D,S,P)] interface attempts to make a
** new [sqlite3_snapshot] object that records the current state of
** schema S in database connection D.  ^On success, the
** [sqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly
** created [sqlite3_snapshot] object into *P and returns SQLITE_OK.
** If there is not already a read-transaction open on schema S when
** this function is called, one is opened automatically. 
**
** The following must be true for this function to succeed. If any of
** the following statements are false when sqlite3_snapshot_get() is
** called, SQLITE_ERROR is returned. The final value of *P is undefined
** in this case. 
**
** <ul>
**   <li> The database handle must be in [autocommit mode].
**
**   <li> Schema S of [database connection] D must be a [WAL mode] database.


**
**   <li> There must not be a write transaction open on schema S of database
**        connection D.
**
**   <li> One or more transactions must have been written to the current wal
**        file since it was created on disk (by any connection). This means
**        that a snapshot cannot be taken on a wal mode database with no wal 
**        file immediately after it is first opened. At least one transaction
**        must be written to it first.
** </ul>
**
** This function may also return SQLITE_NOMEM.  If it is called with the
** database handle in autocommit mode but fails for some other reason, 
** whether or not a read transaction is opened on schema S is undefined.
**
** The [sqlite3_snapshot] object returned from a successful call to
** [sqlite3_snapshot_get()] must be freed using [sqlite3_snapshot_free()]
** to avoid a memory leak.
**
** The [sqlite3_snapshot_get()] interface is only available when the
** SQLITE_ENABLE_SNAPSHOT compile-time option is used.
................................................................................
** snapshot, and a positive value if P1 is a newer snapshot than P2.
*/
SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
  sqlite3_snapshot *p1,
  sqlite3_snapshot *p2
);

/*
** CAPI3REF: Recover snapshots from a wal file
** EXPERIMENTAL
**
** If all connections disconnect from a database file but do not perform
** a checkpoint, the existing wal file is opened along with the database
** file the next time the database is opened. At this point it is only
** possible to successfully call sqlite3_snapshot_open() to open the most
** recent snapshot of the database (the one at the head of the wal file),
** even though the wal file may contain other valid snapshots for which
** clients have sqlite3_snapshot handles.
**
** This function attempts to scan the wal file associated with database zDb
** of database handle db and make all valid snapshots available to
** sqlite3_snapshot_open(). It is an error if there is already a read
** transaction open on the database, or if the database is not a wal mode
** database.
**
** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
*/
SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);

/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.
*/
#ifdef SQLITE_OMIT_FLOATING_POINT
# undef double
#endif

#ifdef __cplusplus
}  /* End of the 'extern "C"' block */
#endif
#endif /* SQLITE3_H */

Changes to src/test1.c.

2306
2307
2308
2309
2310
2311
2312
































2313
2314
2315
2316
2317
2318
2319
....
2381
2382
2383
2384
2385
2386
2387











































































































2388
2389
2390
2391
2392
2393
2394
....
7535
7536
7537
7538
7539
7540
7541




7542
7543
7544
7545
7546
7547
7548
    if( sqlite3TestMakePointerStr(interp, zBuf, pSnapshot) ) return TCL_ERROR;
    Tcl_SetObjResult(interp, Tcl_NewStringObj(zBuf, -1));
  }
  return TCL_OK;
}
#endif /* SQLITE_ENABLE_SNAPSHOT */

































#ifdef SQLITE_ENABLE_SNAPSHOT
/*
** Usage: sqlite3_snapshot_open DB DBNAME SNAPSHOT
*/
static int SQLITE_TCLAPI test_snapshot_open(
  void * clientData,
  Tcl_Interp *interp,
................................................................................
    return TCL_ERROR;
  }
  p1 = (sqlite3_snapshot*)sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
  p2 = (sqlite3_snapshot*)sqlite3TestTextToPtr(Tcl_GetString(objv[2]));
  res = sqlite3_snapshot_cmp(p1, 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 */
................................................................................
#endif
     { "vfs_current_time_int64",           vfsCurrentTimeInt64,   0 },
#ifdef SQLITE_ENABLE_SNAPSHOT
     { "sqlite3_snapshot_get", test_snapshot_get, 0 },
     { "sqlite3_snapshot_open", test_snapshot_open, 0 },
     { "sqlite3_snapshot_free", test_snapshot_free, 0 },
     { "sqlite3_snapshot_cmp", test_snapshot_cmp, 0 },




#endif
     { "sqlite3_delete_database", test_delete_database, 0 },
  };
  static int bitmask_size = sizeof(Bitmask)*8;
  static int longdouble_size = sizeof(LONGDOUBLE_TYPE);
  int i;
  extern int sqlite3_sync_count, sqlite3_fullsync_count;







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>







2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
....
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
....
7674
7675
7676
7677
7678
7679
7680
7681
7682
7683
7684
7685
7686
7687
7688
7689
7690
7691
    if( sqlite3TestMakePointerStr(interp, zBuf, pSnapshot) ) return TCL_ERROR;
    Tcl_SetObjResult(interp, Tcl_NewStringObj(zBuf, -1));
  }
  return TCL_OK;
}
#endif /* SQLITE_ENABLE_SNAPSHOT */

#ifdef SQLITE_ENABLE_SNAPSHOT
/*
** Usage: sqlite3_snapshot_recover DB DBNAME
*/
static int SQLITE_TCLAPI test_snapshot_recover(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  int rc;
  sqlite3 *db;
  char *zName;

  if( objc!=3 ){
    Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME");
    return TCL_ERROR;
  }
  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
  zName = Tcl_GetString(objv[2]);

  rc = sqlite3_snapshot_recover(db, zName);
  if( rc!=SQLITE_OK ){
    Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
    return TCL_ERROR;
  }else{
    Tcl_ResetResult(interp);
  }
  return TCL_OK;
}
#endif /* SQLITE_ENABLE_SNAPSHOT */

#ifdef SQLITE_ENABLE_SNAPSHOT
/*
** Usage: sqlite3_snapshot_open DB DBNAME SNAPSHOT
*/
static int SQLITE_TCLAPI test_snapshot_open(
  void * clientData,
  Tcl_Interp *interp,
................................................................................
    return TCL_ERROR;
  }
  p1 = (sqlite3_snapshot*)sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
  p2 = (sqlite3_snapshot*)sqlite3TestTextToPtr(Tcl_GetString(objv[2]));
  res = sqlite3_snapshot_cmp(p1, p2);
  Tcl_SetObjResult(interp, Tcl_NewIntObj(res));
  return TCL_OK;
}
#endif /* SQLITE_ENABLE_SNAPSHOT */

#ifdef SQLITE_ENABLE_SNAPSHOT
/*
** Usage: sqlite3_snapshot_get_blob DB DBNAME
*/
static int SQLITE_TCLAPI test_snapshot_get_blob(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  int rc;
  sqlite3 *db;
  char *zName;
  sqlite3_snapshot *pSnapshot = 0;

  if( objc!=3 ){
    Tcl_WrongNumArgs(interp, 1, objv, "DB DBNAME");
    return TCL_ERROR;
  }
  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
  zName = Tcl_GetString(objv[2]);

  rc = sqlite3_snapshot_get(db, zName, &pSnapshot);
  if( rc!=SQLITE_OK ){
    Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
    return TCL_ERROR;
  }else{
    Tcl_SetObjResult(interp, 
        Tcl_NewByteArrayObj((unsigned char*)pSnapshot, sizeof(sqlite3_snapshot))
    );
    sqlite3_snapshot_free(pSnapshot);
  }
  return TCL_OK;
}
#endif /* SQLITE_ENABLE_SNAPSHOT */

#ifdef SQLITE_ENABLE_SNAPSHOT
  /*
  ** Usage: sqlite3_snapshot_open_blob DB DBNAME SNAPSHOT
*/
static int SQLITE_TCLAPI test_snapshot_open_blob(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  int rc;
  sqlite3 *db;
  char *zName;
  unsigned char *pBlob;
  int nBlob;

  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]);
  pBlob = Tcl_GetByteArrayFromObj(objv[3], &nBlob);
  if( nBlob!=sizeof(sqlite3_snapshot) ){
    Tcl_AppendResult(interp, "bad SNAPSHOT", 0);
    return TCL_ERROR;
  }
  rc = sqlite3_snapshot_open(db, zName, (sqlite3_snapshot*)pBlob);
  if( rc!=SQLITE_OK ){
    Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
    return TCL_ERROR;
  }
  return TCL_OK;
}
#endif /* SQLITE_ENABLE_SNAPSHOT */

#ifdef SQLITE_ENABLE_SNAPSHOT
/*
** Usage: sqlite3_snapshot_cmp_blob SNAPSHOT1 SNAPSHOT2
*/
static int SQLITE_TCLAPI test_snapshot_cmp_blob(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  int res;
  unsigned char *p1;
  unsigned char *p2;
  int n1;
  int n2;

  if( objc!=3 ){
    Tcl_WrongNumArgs(interp, 1, objv, "SNAPSHOT1 SNAPSHOT2");
    return TCL_ERROR;
  }

  p1 = Tcl_GetByteArrayFromObj(objv[1], &n1);
  p2 = Tcl_GetByteArrayFromObj(objv[2], &n2);

  if( n1!=sizeof(sqlite3_snapshot) || n1!=n2 ){
    Tcl_AppendResult(interp, "bad SNAPSHOT", 0);
    return TCL_ERROR;
  }

  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 */
................................................................................
#endif
     { "vfs_current_time_int64",           vfsCurrentTimeInt64,   0 },
#ifdef SQLITE_ENABLE_SNAPSHOT
     { "sqlite3_snapshot_get", test_snapshot_get, 0 },
     { "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 },
  };
  static int bitmask_size = sizeof(Bitmask)*8;
  static int longdouble_size = sizeof(LONGDOUBLE_TYPE);
  int i;
  extern int sqlite3_sync_count, sqlite3_fullsync_count;

Changes to src/wal.c.

2375
2376
2377
2378
2379
2380
2381














































































2382
2383
2384
2385
2386
2387
2388
....
2437
2438
2439
2440
2441
2442
2443
2444




2445
2446
2447
2448
2449
2450
2451
....
3389
3390
3391
3392
3393
3394
3395

3396
3397
3398




3399
3400
3401
3402
3403
3404
3405
  }else{
    assert( mxReadMark<=pWal->hdr.mxFrame );
    pWal->readLock = (i16)mxI;
  }
  return rc;
}















































































/*
** Begin a read transaction on the database.
**
** This routine used to be called sqlite3OpenSnapshot() and with good reason:
** it takes a snapshot of the state of the WAL and wal-index for the current
** instant in time.  The current thread will continue to use this snapshot.
** Other threads might append new content to the WAL and wal-index but
................................................................................
      /* It is possible that there is a checkpointer thread running 
      ** concurrent with this code. If this is the case, it may be that the
      ** checkpointer has already determined that it will checkpoint 
      ** snapshot X, where X is later in the wal file than pSnapshot, but 
      ** has not yet set the pInfo->nBackfillAttempted variable to indicate 
      ** its intent. To avoid the race condition this leads to, ensure that
      ** there is no checkpointer process by taking a shared CKPT lock 
      ** before checking pInfo->nBackfillAttempted.  */




      rc = walLockShared(pWal, WAL_CKPT_LOCK);

      if( rc==SQLITE_OK ){
        /* Check that the wal file has not been wrapped. Assuming that it has
        ** not, also check that no checkpointer has attempted to checkpoint any
        ** frames beyond pSnapshot->mxFrame. If either of these conditions are
        ** true, return SQLITE_BUSY_SNAPSHOT. Otherwise, overwrite pWal->hdr
................................................................................
/* Create a snapshot object.  The content of a snapshot is opaque to
** every other subsystem, so the WAL module can put whatever it needs
** in the object.
*/
int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot){
  int rc = SQLITE_OK;
  WalIndexHdr *pRet;


  assert( pWal->readLock>=0 && pWal->writeLock==0 );





  pRet = (WalIndexHdr*)sqlite3_malloc(sizeof(WalIndexHdr));
  if( pRet==0 ){
    rc = SQLITE_NOMEM_BKPT;
  }else{
    memcpy(pRet, &pWal->hdr, sizeof(WalIndexHdr));
    *ppSnapshot = (sqlite3_snapshot*)pRet;
  }







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|
>
>
>
>







 







>



>
>
>
>







2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
....
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
....
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
  }else{
    assert( mxReadMark<=pWal->hdr.mxFrame );
    pWal->readLock = (i16)mxI;
  }
  return rc;
}

#ifdef SQLITE_ENABLE_SNAPSHOT
/*
** Attempt to reduce the value of the WalCkptInfo.nBackfillAttempted 
** variable so that older snapshots can be accessed. To do this, loop
** through all wal frames from nBackfillAttempted to (nBackfill+1), 
** comparing their content to the corresponding page with the database
** file, if any. Set nBackfillAttempted to the frame number of the
** first frame for which the wal file content matches the db file.
**
** This is only really safe if the file-system is such that any page 
** writes made by earlier checkpointers were atomic operations, which 
** is not always true. It is also possible that nBackfillAttempted
** may be left set to a value larger than expected, if a wal frame
** contains content that duplicate of an earlier version of the same
** page.
**
** SQLITE_OK is returned if successful, or an SQLite error code if an
** error occurs. It is not an error if nBackfillAttempted cannot be
** decreased at all.
*/
int sqlite3WalSnapshotRecover(Wal *pWal){
  int rc;

  assert( pWal->readLock>=0 );
  rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1);
  if( rc==SQLITE_OK ){
    volatile WalCkptInfo *pInfo = walCkptInfo(pWal);
    int szPage = (int)pWal->szPage;
    i64 szDb;                   /* Size of db file in bytes */

    rc = sqlite3OsFileSize(pWal->pDbFd, &szDb);
    if( rc==SQLITE_OK ){
      void *pBuf1 = sqlite3_malloc(szPage);
      void *pBuf2 = sqlite3_malloc(szPage);
      if( pBuf1==0 || pBuf2==0 ){
        rc = SQLITE_NOMEM;
      }else{
        u32 i = pInfo->nBackfillAttempted;
        for(i=pInfo->nBackfillAttempted; i>pInfo->nBackfill; i--){
          volatile ht_slot *dummy;
          volatile u32 *aPgno;      /* Array of page numbers */
          u32 iZero;                /* Frame corresponding to aPgno[0] */
          u32 pgno;                 /* Page number in db file */
          i64 iDbOff;               /* Offset of db file entry */
          i64 iWalOff;              /* Offset of wal file entry */

          rc = walHashGet(pWal, walFramePage(i), &dummy, &aPgno, &iZero);
          if( rc!=SQLITE_OK ) break;
          pgno = aPgno[i-iZero];
          iDbOff = (i64)(pgno-1) * szPage;

          if( iDbOff+szPage<=szDb ){
            iWalOff = walFrameOffset(i, szPage) + WAL_FRAME_HDRSIZE;
            rc = sqlite3OsRead(pWal->pWalFd, pBuf1, szPage, iWalOff);

            if( rc==SQLITE_OK ){
              rc = sqlite3OsRead(pWal->pDbFd, pBuf2, szPage, iDbOff);
            }

            if( rc!=SQLITE_OK || 0==memcmp(pBuf1, pBuf2, szPage) ){
              break;
            }
          }

          pInfo->nBackfillAttempted = i-1;
        }
      }

      sqlite3_free(pBuf1);
      sqlite3_free(pBuf2);
    }
    walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1);
  }

  return rc;
}
#endif /* SQLITE_ENABLE_SNAPSHOT */

/*
** Begin a read transaction on the database.
**
** This routine used to be called sqlite3OpenSnapshot() and with good reason:
** it takes a snapshot of the state of the WAL and wal-index for the current
** instant in time.  The current thread will continue to use this snapshot.
** Other threads might append new content to the WAL and wal-index but
................................................................................
      /* It is possible that there is a checkpointer thread running 
      ** concurrent with this code. If this is the case, it may be that the
      ** checkpointer has already determined that it will checkpoint 
      ** snapshot X, where X is later in the wal file than pSnapshot, but 
      ** has not yet set the pInfo->nBackfillAttempted variable to indicate 
      ** its intent. To avoid the race condition this leads to, ensure that
      ** there is no checkpointer process by taking a shared CKPT lock 
      ** before checking pInfo->nBackfillAttempted.  
      **
      ** TODO: Does the aReadMark[] lock prevent a checkpointer from doing
      **       this already?
      */
      rc = walLockShared(pWal, WAL_CKPT_LOCK);

      if( rc==SQLITE_OK ){
        /* Check that the wal file has not been wrapped. Assuming that it has
        ** not, also check that no checkpointer has attempted to checkpoint any
        ** frames beyond pSnapshot->mxFrame. If either of these conditions are
        ** true, return SQLITE_BUSY_SNAPSHOT. Otherwise, overwrite pWal->hdr
................................................................................
/* Create a snapshot object.  The content of a snapshot is opaque to
** every other subsystem, so the WAL module can put whatever it needs
** in the object.
*/
int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot){
  int rc = SQLITE_OK;
  WalIndexHdr *pRet;
  static const u32 aZero[4] = { 0, 0, 0, 0 };

  assert( pWal->readLock>=0 && pWal->writeLock==0 );

  if( memcmp(&pWal->hdr.aFrameCksum[0],aZero,16)==0 ){
    *ppSnapshot = 0;
    return SQLITE_ERROR;
  }
  pRet = (WalIndexHdr*)sqlite3_malloc(sizeof(WalIndexHdr));
  if( pRet==0 ){
    rc = SQLITE_NOMEM_BKPT;
  }else{
    memcpy(pRet, &pWal->hdr, sizeof(WalIndexHdr));
    *ppSnapshot = (sqlite3_snapshot*)pRet;
  }

Changes to src/wal.h.

127
128
129
130
131
132
133

134
135
136
137
138
139
140
** WAL module is using shared-memory, return false. 
*/
int sqlite3WalHeapMemory(Wal *pWal);

#ifdef SQLITE_ENABLE_SNAPSHOT
int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot);
void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot);

#endif

#ifdef SQLITE_ENABLE_ZIPVFS
/* If the WAL file is not empty, return the number of bytes of content
** stored in each frame (i.e. the db page-size when the WAL was created).
*/
int sqlite3WalFramesize(Wal *pWal);







>







127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
** WAL module is using shared-memory, return false. 
*/
int sqlite3WalHeapMemory(Wal *pWal);

#ifdef SQLITE_ENABLE_SNAPSHOT
int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot);
void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot);
int sqlite3WalSnapshotRecover(Wal *pWal);
#endif

#ifdef SQLITE_ENABLE_ZIPVFS
/* If the WAL file is not empty, return the number of bytes of content
** stored in each frame (i.e. the db page-size when the WAL was created).
*/
int sqlite3WalFramesize(Wal *pWal);

Changes to test/snapshot.test.

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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438












































# "PRAGMA journal_mode=memory", which fails if the database is in wal mode
# and there are one or more existing connections.
if {[permutation]=="inmemory_journal"} {
  finish_test
  return
}

#-------------------------------------------------------------------------
# Check some error conditions in snapshot_get(). It is an error if:
#
#  1) snapshot_get() is called on a non-WAL database, or
#  2) there is an open write transaction on the database.
#
do_execsql_test 1.0 {
  CREATE TABLE t1(a, b);
  INSERT INTO t1 VALUES(1, 2);
  INSERT INTO t1 VALUES(3, 4);
}

do_test 1.1.1 {
  execsql { BEGIN; SELECT * FROM t1; }
  list [catch { sqlite3_snapshot_get db main } msg] $msg
} {1 SQLITE_ERROR}
do_execsql_test 1.1.2 COMMIT

do_test 1.2.1 {
  execsql {
    PRAGMA journal_mode = WAL;
    BEGIN;
      INSERT INTO t1 VALUES(5, 6);
      INSERT INTO t1 VALUES(7, 8);
  }
  list [catch { sqlite3_snapshot_get db main } msg] $msg
} {1 SQLITE_ERROR}
do_execsql_test 1.3.2 COMMIT

#-------------------------------------------------------------------------
# Check that a simple case works. Reuse the database created by the
# block of tests above.
#
do_execsql_test 2.1.0 {
  BEGIN;
    SELECT * FROM t1;
} {1 2 3 4 5 6 7 8}

do_test 2.1.1 {
  set snapshot [sqlite3_snapshot_get db main]
  execsql {
    COMMIT;
    INSERT INTO t1 VALUES(9, 10);
    SELECT * FROM t1;
  }
} {1 2 3 4 5 6 7 8 9 10}

do_test 2.1.2 {
  execsql BEGIN
  sqlite3_snapshot_open db main $snapshot
  execsql {
    SELECT * FROM t1;
  }
} {1 2 3 4 5 6 7 8}

do_test 2.1.3 {
  sqlite3_snapshot_free $snapshot
  execsql COMMIT
} {}

do_test 2.2.0 {
  sqlite3 db2 test.db
  execsql {
    BEGIN;
      SELECT * FROM t1;
  } db2
} {1 2 3 4 5 6 7 8 9 10}

do_test 2.2.1 {
  set snapshot [sqlite3_snapshot_get db2 main]
  execsql {
    INSERT INTO t1 VALUES(11, 12);
    SELECT * FROM t1;
  }
} {1 2 3 4 5 6 7 8 9 10 11 12}

do_test 2.2.2 {
  execsql BEGIN
  sqlite3_snapshot_open db main $snapshot
  execsql {
    SELECT * FROM t1;
  }
} {1 2 3 4 5 6 7 8 9 10}

do_test 2.2.3 {
  sqlite3_snapshot_free $snapshot
  execsql COMMIT
  execsql COMMIT db2
  db2 close
} {}

do_test 2.3.1 {
  execsql { DELETE FROM t1 WHERE a>6 }
  set snapshot [sqlite3_snapshot_get db main]
  execsql {
    INSERT INTO t1 VALUES('a', 'b');
    INSERT INTO t1 VALUES('c', 'd');
    SELECT * FROM t1;
  }
} {1 2 3 4 5 6 a b c d}
do_test 2.3.2 {
  execsql BEGIN
  sqlite3_snapshot_open db main $snapshot
  execsql { SELECT * FROM t1 }
} {1 2 3 4 5 6}

do_test 2.3.3 {
  catchsql {
    INSERT INTO t1 VALUES('x','y')
  }
} {1 {database is locked}}
do_test 2.3.4 {
  execsql COMMIT
  sqlite3_snapshot_free $snapshot
} {}

#-------------------------------------------------------------------------
# Check some errors in sqlite3_snapshot_open(). It is an error if:
#
#   1) the db is in auto-commit mode,
#   2) the db has an open (read or write) transaction,
#   3) the db is not a wal database,
#
# Reuse the database created by earlier tests.
#
do_execsql_test 3.0.0 {
  CREATE TABLE t2(x, y);
  INSERT INTO t2 VALUES('a', 'b');
  INSERT INTO t2 VALUES('c', 'd');
  BEGIN;
    SELECT * FROM t2;
} {a b c d}
do_test 3.0.1 {
  set snapshot [sqlite3_snapshot_get db main]
  execsql { COMMIT }
  execsql { INSERT INTO t2 VALUES('e', 'f'); }
} {}

do_test 3.1 {
  list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg
} {1 SQLITE_ERROR}

do_test 3.2.1 {
  execsql {
    BEGIN;
      SELECT * FROM t2;
  }
} {a b c d e f}
do_test 3.2.2 {
  list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg
} {1 SQLITE_ERROR}

do_test 3.2.3 {
  execsql {
    COMMIT;
    BEGIN;
      INSERT INTO t2 VALUES('g', 'h');
  }
  list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg
} {1 SQLITE_ERROR}
do_execsql_test 3.2.4 COMMIT

do_test 3.3.1 {
  execsql { PRAGMA journal_mode = DELETE }
  execsql { BEGIN }
  list [catch {sqlite3_snapshot_open db main $snapshot } msg] $msg
} {1 SQLITE_ERROR}

do_test 3.3.2 {
  sqlite3_snapshot_free $snapshot
  execsql COMMIT
} {}

#-------------------------------------------------------------------------
# Check that SQLITE_BUSY_SNAPSHOT is returned if the specified snapshot
# no longer exists because the wal file has been checkpointed.
#
#   1. Reading a snapshot from the middle of a wal file is not possible
#      after the wal file has been checkpointed.
#
#   2. That a snapshot from the end of a wal file can not be read once
#      the wal file has been wrapped.
#
do_execsql_test 4.1.0 {
  PRAGMA journal_mode = wal;
  CREATE TABLE t3(i, j);
  INSERT INTO t3 VALUES('o', 't');
  INSERT INTO t3 VALUES('t', 'f');
  BEGIN;
    SELECT * FROM t3;
} {wal o t t f}

do_test 4.1.1 {
  set snapshot [sqlite3_snapshot_get db main]
  execsql COMMIT
} {}
do_test 4.1.2 {
  execsql { 
    INSERT INTO t3 VALUES('f', 's'); 
    BEGIN;
  }
  sqlite3_snapshot_open db main $snapshot
  execsql { SELECT * FROM t3 }
} {o t t f}

do_test 4.1.3 {
  execsql { 
    COMMIT;
    PRAGMA wal_checkpoint;
    BEGIN;
  }
  list [catch {sqlite3_snapshot_open db main $snapshot} msg] $msg
} {1 SQLITE_BUSY_SNAPSHOT}
do_test 4.1.4 {
  sqlite3_snapshot_free $snapshot
  execsql COMMIT
} {}

do_test 4.2.1 {
  execsql {
    INSERT INTO t3 VALUES('s', 'e');
    INSERT INTO t3 VALUES('n', 't');
    BEGIN;
      SELECT * FROM t3;
  }
} {o t t f f s s e n t}
do_test 4.2.2 {
  set snapshot [sqlite3_snapshot_get db main]
  execsql {
    COMMIT;
    PRAGMA wal_checkpoint;
    BEGIN;
  }
  sqlite3_snapshot_open db main $snapshot
  execsql { SELECT * FROM t3 }
} {o t t f f s s e n t}
do_test 4.2.3 {
  execsql {
    COMMIT;
    INSERT INTO t3 VALUES('e', 't');
    BEGIN;
  }
  list [catch {sqlite3_snapshot_open db main $snapshot} msg] $msg
} {1 SQLITE_BUSY_SNAPSHOT}
do_test 4.2.4 {
  sqlite3_snapshot_free $snapshot
} {}

#-------------------------------------------------------------------------
# Check that SQLITE_BUSY is returned if a checkpoint is running when
# sqlite3_snapshot_open() is called.
#
reset_db
db close
testvfs tvfs
sqlite3 db test.db -vfs tvfs

do_execsql_test 5.1 {
  PRAGMA journal_mode = wal;
  CREATE TABLE x1(x, xx, xxx);
  INSERT INTO x1 VALUES('z', 'zz', 'zzz');
  BEGIN;
    SELECT * FROM x1;
} {wal z zz zzz}

do_test 5.2 {
  set ::snapshot [sqlite3_snapshot_get db main]
  sqlite3 db2 test.db -vfs tvfs
  execsql {
    INSERT INTO x1 VALUES('a', 'aa', 'aaa');
    COMMIT;
  }
} {}

set t53 0
proc write_callback {args} {
  do_test 5.3.[incr ::t53] {
    execsql BEGIN
    list [catch { sqlite3_snapshot_open db main $::snapshot } msg] $msg
  } {1 SQLITE_BUSY}
  catchsql COMMIT
}

tvfs filter xWrite
tvfs script write_callback
db2 eval { PRAGMA wal_checkpoint }
db close
db2 close
tvfs delete
sqlite3_snapshot_free $snapshot

#-------------------------------------------------------------------------
# Test that sqlite3_snapshot_get() may be called immediately after
# "BEGIN; PRAGMA user_version;". And that sqlite3_snapshot_open() may
# be called after opening the db handle and running the script
# "PRAGMA user_version; BEGIN".
reset_db
do_execsql_test 6.1 {
  PRAGMA journal_mode = wal;
  CREATE TABLE x1(x, xx, xxx);
  INSERT INTO x1 VALUES('z', 'zz', 'zzz');
  BEGIN;
    PRAGMA user_version;
} {wal 0}
do_test 6.2 {
  set ::snapshot [sqlite3_snapshot_get db main]
  execsql {
    INSERT INTO x1 VALUES('a', 'aa', 'aaa');
    COMMIT;
  }
} {}
do_test 6.3 {
  sqlite3 db2 test.db 
  db2 eval "PRAGMA user_version ; BEGIN"
  sqlite3_snapshot_open db2 main $::snapshot
  db2 eval { SELECT * FROM x1 }
} {z zz zzz}
do_test 6.4 {
  db2 close
  sqlite3 db2 test.db 
  db2 eval "PRAGMA application_id"
  db2 eval "BEGIN"
  sqlite3_snapshot_open db2 main $::snapshot
  db2 eval { SELECT * FROM x1 }
} {z zz zzz}

do_test 6.5 {
  db2 close
  sqlite3 db2 test.db 
  db2 eval "BEGIN"
  list [catch {sqlite3_snapshot_open db2 main $::snapshot} msg] $msg
} {1 SQLITE_ERROR}

sqlite3_snapshot_free $snapshot

#-------------------------------------------------------------------------
# The following tests investigate the sqlite3_snapshot_cmp() API.
#

# Compare snapshots $p1 and $p2, checking that the result is $r.
#
proc do_snapshot_cmp_test {tn p1 p2 r} {
  uplevel [list do_test $tn.1 [list sqlite3_snapshot_cmp $p1 $p2] $r]
  uplevel [list do_test $tn.2 [list sqlite3_snapshot_cmp $p2 $p1] [expr $r*-1]]
  uplevel [list do_test $tn.3 [list sqlite3_snapshot_cmp $p1 $p1] 0]
  uplevel [list do_test $tn.4 [list sqlite3_snapshot_cmp $p2 $p2] 0]
}

catch { db2 close }
reset_db

do_execsql_test 7.1 {
  PRAGMA journal_mode = wal;
  CREATE TABLE t1(x);
} wal

do_test 7.1.2 {
  execsql { BEGIN ; PRAGMA application_id }
  set p1 [sqlite3_snapshot_get db main]
  execsql {
    INSERT INTO t1 VALUES(10);
    COMMIT;
  }
  execsql { BEGIN ; PRAGMA application_id }
  set p2 [sqlite3_snapshot_get db main]
  execsql COMMIT
} {}

do_snapshot_cmp_test 7.1.3 $p1 $p2 -1
sqlite3_snapshot_free $p1
sqlite3_snapshot_free $p2

do_execsql_test 7.2.1 {
  INSERT INTO t1 VALUES(11);
  INSERT INTO t1 VALUES(12);
  INSERT INTO t1 VALUES(13);
  BEGIN; 
    PRAGMA application_id;
} {0}
do_test 7.2.2 {
  set p1 [sqlite3_snapshot_get db main]
  execsql {
    COMMIT;
    INSERT INTO t1 VALUES(14);
    PRAGMA wal_checkpoint;
    BEGIN;
      PRAGMA application_id;
  }
  set p2 [sqlite3_snapshot_get db main]
  execsql COMMIT
} {}

do_snapshot_cmp_test 7.2.3 $p1 $p2 -1
sqlite3_snapshot_free $p2

do_test 7.3.1 {
  execsql {
    INSERT INTO t1 VALUES(14);
    BEGIN;
      PRAGMA application_id;
  }
  set p2 [sqlite3_snapshot_get db main]
  execsql COMMIT
} {}

do_snapshot_cmp_test 7.3.2 $p1 $p2 -1
sqlite3_snapshot_free $p1
sqlite3_snapshot_free $p2

finish_test



















































|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
# "PRAGMA journal_mode=memory", which fails if the database is in wal mode
# and there are one or more existing connections.
if {[permutation]=="inmemory_journal"} {
  finish_test
  return
}

foreach {tn tcl} {
  1 {
    proc snapshot_get {DB DBNAME} {
      uplevel [list sqlite3_snapshot_get $DB $DBNAME]
    }
    proc snapshot_open {DB DBNAME SNAPSHOT} {
      uplevel [list sqlite3_snapshot_open $DB $DBNAME $SNAPSHOT]
    }
    proc snapshot_free {SNAPSHOT} {
      uplevel [list sqlite3_snapshot_free $SNAPSHOT]
    }
    proc snapshot_cmp {SNAPSHOT1 SNAPSHOT2} {
      uplevel [list sqlite3_snapshot_cmp $SNAPSHOT1 $SNAPSHOT2]
    }
  }

  2 {
    proc snapshot_get {DB DBNAME} {
      uplevel [list sqlite3_snapshot_get_blob $DB $DBNAME]
    }
    proc snapshot_open {DB DBNAME SNAPSHOT} {
      uplevel [list sqlite3_snapshot_open_blob $DB $DBNAME $SNAPSHOT]
    }
    proc snapshot_free {SNAPSHOT} {
    }
    proc snapshot_cmp {SNAPSHOT1 SNAPSHOT2} {
      uplevel [list sqlite3_snapshot_cmp_blob $SNAPSHOT1 $SNAPSHOT2]
    }
  }
} {

  reset_db
  eval $tcl

  #-------------------------------------------------------------------------
  # Check some error conditions in snapshot_get(). It is an error if:
  #
  #  1) snapshot_get() is called on a non-WAL database, or
  #  2) there is an open write transaction on the database.
  #  3) the database handle is in auto-commit mode
  #
  do_execsql_test $tn.1.0 {
    CREATE TABLE t1(a, b);
    INSERT INTO t1 VALUES(1, 2);
    INSERT INTO t1 VALUES(3, 4);
  }

  do_test $tn.1.1.1 {
    execsql { BEGIN; SELECT * FROM t1; }
    list [catch { snapshot_get db main } msg] $msg
  } {1 SQLITE_ERROR}
  do_execsql_test $tn.1.1.2 COMMIT

  do_test $tn.1.2.1 {
    execsql {
      PRAGMA journal_mode = WAL;
      BEGIN;
        INSERT INTO t1 VALUES(5, 6);
        INSERT INTO t1 VALUES(7, 8);
    }
    list [catch { snapshot_get db main } msg] $msg
  } {1 SQLITE_ERROR}
  do_execsql_test $tn.1.2.2 COMMIT

  do_test $tn.1.3.1 {
    list [catch { snapshot_get db main } msg] $msg
  } {1 SQLITE_ERROR}
  do_test $tn.1.3.2 {
    db trans { set snap [snapshot_get db main] }
    snapshot_free $snap
  } {}

  #-------------------------------------------------------------------------
  # Check that a simple case works. Reuse the database created by the
  # block of tests above.
  #
  do_execsql_test $tn.2.1.0 {
    BEGIN;
      SELECT * FROM t1;
  } {1 2 3 4 5 6 7 8}

  do_test $tn.2.1.1 {
    set snapshot [snapshot_get db main]
    execsql {
      COMMIT;
      INSERT INTO t1 VALUES(9, 10);
      SELECT * FROM t1;
    }
  } {1 2 3 4 5 6 7 8 9 10}

  do_test $tn.2.1.2 {
    execsql BEGIN
    snapshot_open db main $snapshot
    execsql {
      SELECT * FROM t1;
    }
  } {1 2 3 4 5 6 7 8}

  do_test $tn.2.1.3 {
    snapshot_free $snapshot
    execsql COMMIT
  } {}

  do_test $tn.2.2.0 {
    sqlite3 db2 test.db
    execsql {
      BEGIN;
        SELECT * FROM t1;
    } db2
  } {1 2 3 4 5 6 7 8 9 10}

  do_test $tn.2.2.1 {
    set snapshot [snapshot_get db2 main]
    execsql {
      INSERT INTO t1 VALUES(11, 12);
      SELECT * FROM t1;
    }
  } {1 2 3 4 5 6 7 8 9 10 11 12}

  do_test $tn.2.2.2 {
    execsql BEGIN
    snapshot_open db main $snapshot
    execsql {
      SELECT * FROM t1;
    }
  } {1 2 3 4 5 6 7 8 9 10}

  do_test $tn.2.2.3 {
    snapshot_free $snapshot
    execsql COMMIT
    execsql COMMIT db2
    db2 close
  } {}

  do_test $tn.2.3.1 {
    execsql { DELETE FROM t1 WHERE a>6 }
    db trans { set snapshot [snapshot_get db main] }
    execsql {
      INSERT INTO t1 VALUES('a', 'b');
      INSERT INTO t1 VALUES('c', 'd');
      SELECT * FROM t1;
    }
  } {1 2 3 4 5 6 a b c d}
  do_test $tn.2.3.2 {
    execsql BEGIN
    snapshot_open db main $snapshot
    execsql { SELECT * FROM t1 }
  } {1 2 3 4 5 6}

  do_test $tn.2.3.3 {
    catchsql {
      INSERT INTO t1 VALUES('x','y')
    }
  } {1 {database is locked}}
  do_test $tn.2.3.4 {
    execsql COMMIT
    snapshot_free $snapshot
  } {}

  #-------------------------------------------------------------------------
  # Check some errors in snapshot_open(). It is an error if:
  #
  #   1) the db is in auto-commit mode,
  #   2) the db has an open (read or write) transaction,
  #   3) the db is not a wal database,
  #
  # Reuse the database created by earlier tests.
  #
  do_execsql_test $tn.3.0.0 {
    CREATE TABLE t2(x, y);
    INSERT INTO t2 VALUES('a', 'b');
    INSERT INTO t2 VALUES('c', 'd');
    BEGIN;
      SELECT * FROM t2;
  } {a b c d}
  do_test $tn.3.0.1 {
    set snapshot [snapshot_get db main]
    execsql { COMMIT }
    execsql { INSERT INTO t2 VALUES('e', 'f'); }
  } {}

  do_test $tn.3.1 {
    list [catch {snapshot_open db main $snapshot } msg] $msg
  } {1 SQLITE_ERROR}

  do_test $tn.3.2.1 {
    execsql {
      BEGIN;
        SELECT * FROM t2;
    }
  } {a b c d e f}
  do_test $tn.3.2.2 {
    list [catch {snapshot_open db main $snapshot } msg] $msg
  } {1 SQLITE_ERROR}

  do_test $tn.3.2.3 {
    execsql {
      COMMIT;
      BEGIN;
        INSERT INTO t2 VALUES('g', 'h');
    }
    list [catch {snapshot_open db main $snapshot } msg] $msg
  } {1 SQLITE_ERROR}
  do_execsql_test $tn.3.2.4 COMMIT

  do_test $tn.3.3.1 {
    execsql { PRAGMA journal_mode = DELETE }
    execsql { BEGIN }
    list [catch {snapshot_open db main $snapshot } msg] $msg
  } {1 SQLITE_ERROR}

  do_test $tn.$tn.3.3.2 {
    snapshot_free $snapshot
    execsql COMMIT
  } {}

  #-------------------------------------------------------------------------
  # Check that SQLITE_BUSY_SNAPSHOT is returned if the specified snapshot
  # no longer exists because the wal file has been checkpointed.
  #
  #   1. Reading a snapshot from the middle of a wal file is not possible
  #      after the wal file has been checkpointed.
  #
  #   2. That a snapshot from the end of a wal file can not be read once
  #      the wal file has been wrapped.
  #
  do_execsql_test $tn.4.1.0 {
    PRAGMA journal_mode = wal;
    CREATE TABLE t3(i, j);
    INSERT INTO t3 VALUES('o', 't');
    INSERT INTO t3 VALUES('t', 'f');
    BEGIN;
      SELECT * FROM t3;
  } {wal o t t f}

  do_test $tn.4.1.1 {
    set snapshot [snapshot_get db main]
    execsql COMMIT
  } {}
  do_test $tn.4.1.2 {
    execsql { 
      INSERT INTO t3 VALUES('f', 's'); 
      BEGIN;
    }
    snapshot_open db main $snapshot
    execsql { SELECT * FROM t3 }
  } {o t t f}

  do_test $tn.4.1.3 {
    execsql { 
      COMMIT;
      PRAGMA wal_checkpoint;
      BEGIN;
    }
    list [catch {snapshot_open db main $snapshot} msg] $msg
  } {1 SQLITE_BUSY_SNAPSHOT}
  do_test $tn.4.1.4 {
    snapshot_free $snapshot
    execsql COMMIT
  } {}

  do_test $tn.4.2.1 {
    execsql {
      INSERT INTO t3 VALUES('s', 'e');
      INSERT INTO t3 VALUES('n', 't');
      BEGIN;
        SELECT * FROM t3;
    }
  } {o t t f f s s e n t}
  do_test $tn.4.2.2 {
    set snapshot [snapshot_get db main]
    execsql {
      COMMIT;
      PRAGMA wal_checkpoint;
      BEGIN;
    }
    snapshot_open db main $snapshot
    execsql { SELECT * FROM t3 }
  } {o t t f f s s e n t}
  do_test $tn.4.2.3 {
    execsql {
      COMMIT;
      INSERT INTO t3 VALUES('e', 't');
      BEGIN;
    }
    list [catch {snapshot_open db main $snapshot} msg] $msg
  } {1 SQLITE_BUSY_SNAPSHOT}
  do_test $tn.4.2.4 {
    snapshot_free $snapshot
  } {}

  #-------------------------------------------------------------------------
  # Check that SQLITE_BUSY is returned if a checkpoint is running when
  # sqlite3_snapshot_open() is called.
  #
  reset_db
  db close
  testvfs tvfs
  sqlite3 db test.db -vfs tvfs

  do_execsql_test $tn.5.1 {
    PRAGMA journal_mode = wal;
    CREATE TABLE x1(x, xx, xxx);
    INSERT INTO x1 VALUES('z', 'zz', 'zzz');
    BEGIN;
      SELECT * FROM x1;
  } {wal z zz zzz}

  do_test $tn.5.2 {
    set ::snapshot [snapshot_get db main]
    sqlite3 db2 test.db -vfs tvfs
    execsql {
      INSERT INTO x1 VALUES('a', 'aa', 'aaa');
      COMMIT;
    }
  } {}

  set t53 0
  proc write_callback {args} {
    do_test $tn.5.3.[incr ::t53] {
      execsql BEGIN
      list [catch { snapshot_open db main $::snapshot } msg] $msg
    } {1 SQLITE_BUSY}
    catchsql COMMIT
  }

  tvfs filter xWrite
  tvfs script write_callback
  db2 eval { PRAGMA wal_checkpoint }
  db close
  db2 close
  tvfs delete
  snapshot_free $snapshot

  #-------------------------------------------------------------------------
  # Test that sqlite3_snapshot_get() may be called immediately after
  # "BEGIN; PRAGMA user_version;". And that sqlite3_snapshot_open() may
  # be called after opening the db handle and running the script
  # "PRAGMA user_version; BEGIN".
  reset_db
  do_execsql_test $tn.6.1 {
    PRAGMA journal_mode = wal;
    CREATE TABLE x1(x, xx, xxx);
    INSERT INTO x1 VALUES('z', 'zz', 'zzz');
    BEGIN;
      PRAGMA user_version;
  } {wal 0}
  do_test $tn.6.2 {
    set ::snapshot [snapshot_get db main]
    execsql {
      INSERT INTO x1 VALUES('a', 'aa', 'aaa');
      COMMIT;
    }
  } {}
  do_test $tn.6.3 {
    sqlite3 db2 test.db 
    db2 eval "PRAGMA user_version ; BEGIN"
    snapshot_open db2 main $::snapshot
    db2 eval { SELECT * FROM x1 }
  } {z zz zzz}
  do_test $tn.6.4 {
    db2 close
    sqlite3 db2 test.db 
    db2 eval "PRAGMA application_id"
    db2 eval "BEGIN"
    snapshot_open db2 main $::snapshot
    db2 eval { SELECT * FROM x1 }
  } {z zz zzz}

  do_test $tn.6.5 {
    db2 close
    sqlite3 db2 test.db 
    db2 eval "BEGIN"
    list [catch {snapshot_open db2 main $::snapshot} msg] $msg
  } {1 SQLITE_ERROR}

  snapshot_free $snapshot

  #-------------------------------------------------------------------------
  # The following tests investigate the sqlite3_snapshot_cmp() API.
  #

  # Compare snapshots $p1 and $p2, checking that the result is $r.
  #
  proc do_snapshot_cmp_test {tn p1 p2 r} {
    uplevel [list do_test $tn.1 [list snapshot_cmp $p1 $p2] $r]
    uplevel [list do_test $tn.2 [list snapshot_cmp $p2 $p1] [expr $r*-1]]
    uplevel [list do_test $tn.3 [list snapshot_cmp $p1 $p1] 0]
    uplevel [list do_test $tn.4 [list snapshot_cmp $p2 $p2] 0]
  }

  catch { db2 close }
  reset_db

  do_execsql_test $tn.7.1 {
    PRAGMA journal_mode = wal;
    CREATE TABLE t1(x);
  } wal

  do_test $tn.7.1.2 {
    execsql { BEGIN ; PRAGMA application_id }
    set p1 [snapshot_get db main]
    execsql {
      INSERT INTO t1 VALUES(10);
      COMMIT;
    }
    execsql { BEGIN ; PRAGMA application_id }
    set p2 [snapshot_get db main]
    execsql COMMIT
  } {}

  do_snapshot_cmp_test $tn.7.1.3 $p1 $p2 -1
  snapshot_free $p1
  snapshot_free $p2

  do_execsql_test $tn.7.2.1 {
    INSERT INTO t1 VALUES(11);
    INSERT INTO t1 VALUES(12);
    INSERT INTO t1 VALUES(13);
    BEGIN; 
      PRAGMA application_id;
  } {0}
  do_test $tn.7.2.2 {
    set p1 [snapshot_get db main]
    execsql {
      COMMIT;
      INSERT INTO t1 VALUES(14);
      PRAGMA wal_checkpoint;
      BEGIN;
        PRAGMA application_id;
    }
    set p2 [snapshot_get db main]
    execsql COMMIT
  } {}

  do_snapshot_cmp_test $tn.7.2.3 $p1 $p2 -1
  snapshot_free $p2

  do_test $tn.7.3.1 {
    execsql {
      INSERT INTO t1 VALUES(14);
      BEGIN;
        PRAGMA application_id;
    }
    set p2 [snapshot_get db main]
    execsql COMMIT
  } {}

  do_snapshot_cmp_test $tn.7.3.2 $p1 $p2 -1
  snapshot_free $p1
  snapshot_free $p2
}

finish_test

Added test/snapshot2.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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# 2016 November 18
#
# 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.
#
#***********************************************************************
# This file implements regression tests for SQLite library. The focus
# of this file is the sqlite3_snapshot_xxx() APIs.
#

set testdir [file dirname $argv0]
source $testdir/tester.tcl
ifcapable !snapshot {finish_test; return}
set testprefix snapshot2

# This test does not work with the inmemory_journal permutation. The reason
# is that each connection opened as part of this permutation executes
# "PRAGMA journal_mode=memory", which fails if the database is in wal mode
# and there are one or more existing connections.
if {[permutation]=="inmemory_journal"} {
  finish_test
  return
}

#-------------------------------------------------------------------------
# Check that it is not possible to obtain a snapshot immediately after
# a wal mode database with an empty wal file is opened. But it is after
# the file has been written, even by some other connection.
#
do_execsql_test 1.0 {
  PRAGMA journal_mode = wal;
  CREATE TABLE t1(a, b, c);
  INSERT INTO t1 VALUES(1, 2, 3);
  INSERT INTO t1 VALUES(4, 5, 6);
} {wal}

db close
do_test 1.1.1 { list [file exists test.db] [file exists test.db-wal] } {1 0}

sqlite3 db test.db
do_execsql_test 1.1.2 { SELECT * FROM t1 } {1 2 3 4 5 6}

do_test 1.1.3 {
  execsql BEGIN
  list [catch { sqlite3_snapshot_get_blob db main } msg] $msg
} {1 SQLITE_ERROR}
execsql COMMIT

do_test 1.1.4 {
  execsql { INSERT INTO t1 VALUES(7, 8, 9) }
  execsql BEGIN
  string length [sqlite3_snapshot_get_blob db main]
} 48
execsql COMMIT

db close
do_test 1.2.1 { list [file exists test.db] [file exists test.db-wal] } {1 0}

sqlite3 db test.db
do_execsql_test 1.2.2 { SELECT * FROM t1 } {1 2 3 4 5 6 7 8 9}

do_test 1.2.3 {
  execsql BEGIN
  list [catch { sqlite3_snapshot_get_blob db main } msg] $msg
} {1 SQLITE_ERROR}
execsql COMMIT

do_test 1.2.4 {
  sqlite3 db2 test.db
  execsql { INSERT INTO t1 VALUES(10, 11, 12) } db2
  execsql BEGIN
  string length [sqlite3_snapshot_get_blob db main]
} 48
execsql COMMIT
db2 close

#-------------------------------------------------------------------------
# Simple tests for sqlite3_snapshot_recover().
#
reset_db
do_execsql_test 2.0 {
  CREATE TABLE t1(x);
  PRAGMA journal_mode = wal;
  INSERT INTO t1 VALUES(1);
  INSERT INTO t1 VALUES(2);
} {wal}

do_test 2.1 {
  db trans { set snap [sqlite3_snapshot_get_blob db main] }
  sqlite3_db_config db NO_CKPT_ON_CLOSE 1
  db close
  sqlite3 db test.db

  execsql {SELECT * FROM sqlite_master}
  execsql BEGIN
  sqlite3_snapshot_open_blob db main $snap
  execsql COMMIT;
  execsql { INSERT INTO t1 VALUES(3); }
} {}

do_test 2.2 {
  sqlite3_db_config db NO_CKPT_ON_CLOSE 1
  db close
  sqlite3 db test.db

  execsql {SELECT * FROM sqlite_master}
  execsql BEGIN
  list [catch { sqlite3_snapshot_open_blob db main $snap } msg] $msg
} {1 SQLITE_BUSY_SNAPSHOT}

do_test 2.3 {
  execsql COMMIT
  sqlite3_snapshot_recover db main
  execsql BEGIN
  sqlite3_snapshot_open_blob db main $snap
  execsql { SELECT * FROM t1 }
} {1 2}

do_test 2.4 {
  execsql COMMIT
  execsql { SELECT * FROM t1 }
} {1 2 3}

do_test 2.5 {
  execsql { PRAGMA wal_checkpoint }
  sqlite3_db_config db NO_CKPT_ON_CLOSE 1
  db close
  sqlite3 db test.db

  sqlite3_snapshot_recover db main
  execsql BEGIN
  list [catch { sqlite3_snapshot_open_blob db main $snap } msg] $msg
} {1 SQLITE_BUSY_SNAPSHOT}

#-------------------------------------------------------------------------
# Check that calling sqlite3_snapshot_recover() does not confuse the
# pager cache.
reset_db
do_execsql_test 3.0 {
  PRAGMA journal_mode = wal;
  CREATE TABLE t1(x, y);
  INSERT INTO t1 VALUES('a', 'b');
  INSERT INTO t1 VALUES('c', 'd');
} {wal}
do_test 3.1 {
  sqlite3 db2 test.db
  execsql { INSERT INTO t1 VALUES('e', 'f') } db2
  db2 close
  sqlite3_snapshot_recover db main
} {}
do_execsql_test 3.2 {
  SELECT * FROM t1;
} {a b c d e f}

#-------------------------------------------------------------------------
# Check that sqlite3_snapshot_recover() returns an error if it is called
# with an open read-transaction. Or on a database that does not exist. Or
# on the temp database. Or on a db that is not in wal mode.
#
do_test 4.1 {
  sqlite3_snapshot_recover db main
} {}
do_test 4.2 {
  execsql {
    BEGIN;
      SELECT * FROM sqlite_master;
  }
  list [catch { sqlite3_snapshot_recover db main } msg] $msg
} {1 SQLITE_ERROR}
do_test 4.3 {
  execsql COMMIT
  sqlite3_snapshot_recover db main
} {}
do_test 4.4 {
  list [catch { sqlite3_snapshot_recover db aux } msg] $msg
} {1 SQLITE_ERROR}
do_test 4.5 {
  forcedelete test.db2
  execsql {
    ATTACH 'test.db2' AS aux;
    PRAGMA aux.journal_mode = wal;
    CREATE TABLE aux.t2(x, y);
  }
  list [catch { sqlite3_snapshot_recover db aux } msg] $msg
} {0 {}}
do_test 4.6 {
  list [catch { sqlite3_snapshot_recover db temp } msg] $msg
} {1 SQLITE_ERROR}
do_test 4.7 {
  execsql {
    PRAGMA aux.journal_mode = delete;
  }
  list [catch { sqlite3_snapshot_recover db aux } msg] $msg
} {1 SQLITE_ERROR}

finish_test


Changes to test/snapshot_fault.test.

154
155
156
157
158
159
160
161


162








163




















































164
      PRAGMA integrity_check;
    }]
    if {$res != "1 2 3 ok"} { error "res is $res" }
  }

  sqlite3_snapshot_free $::snapshot
}

































































finish_test








>
>
|
>
>
>
>
>
>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
      PRAGMA integrity_check;
    }]
    if {$res != "1 2 3 ok"} { error "res is $res" }
  }

  sqlite3_snapshot_free $::snapshot
}

#-------------------------------------------------------------------------
# Test the handling of faults that occur within sqlite3_snapshot_recover().
#
reset_db
do_execsql_test 4.0 {
  PRAGMA journal_mode = wal;
  CREATE TABLE t1(zzz);
  INSERT INTO t1 VALUES('abc');
  INSERT INTO t1 VALUES('def');
} {wal}
faultsim_save_and_close

do_test 4.0.1 {
  faultsim_restore_and_reopen
  db eval { SELECT * FROM sqlite_master } 
  sqlite3_snapshot_recover db main
} {}
db close

do_faultsim_test 4.0 -faults oom* -prep {
  faultsim_restore_and_reopen
  db eval { SELECT * FROM sqlite_master } 
} -body {
  sqlite3_snapshot_recover db main
} -test {
  faultsim_test_result {0 {}} {1 SQLITE_NOMEM} {1 SQLITE_IOERR_NOMEM}
}

# The following test cases contrive to call sqlite3_snapshot_recover()
# before all pages of the *-shm file have been mapped. This tests an
# extra branch of error handling logic in snapshot_recover().
#
reset_db
do_execsql_test 4.1.0 {
  PRAGMA page_size = 512;
  PRAGMA journal_mode = wal;
  PRAGMA wal_autocheckpoint = 0;
  CREATE TABLE t1(zzz);
  INSERT INTO t1 VALUES(randomblob( 500 * 9500 ));
  PRAGMA user_version = 211;
} {wal 0}

do_test 4.1.1 {
  list [file size test.db-shm] [file size test.db]
} {98304 512}

faultsim_save_and_close
do_faultsim_test 4.1 -faults shm* -prep {
  catch { db2 close } 
  catch { db close } 
  faultsim_restore_and_reopen
  sqlite3 db2 test.db
  db2 eval { SELECT * FROM sqlite_master } 
  db eval BEGIN
  sqlite3_snapshot_get_blob db main
  db eval COMMIT
} -body {
  sqlite3_snapshot_recover db main
} -test {
  faultsim_test_result {0 {}} {1 SQLITE_IOERR}
}



finish_test