diff --git a/Makefile.am b/Makefile.am index 96fbeb963..2ec8fe26b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -177,6 +177,7 @@ unit_test_SOURCES += \ test/unit/test_sm.c \ test/unit/test_tuple.c \ test/unit/test_vfs.c \ + test/unit/test_vfs_extra.c \ test/unit/test_vfs2.c \ test/unit/main.c unit_test_CFLAGS = $(AM_CFLAGS) -Wno-unknown-warning-option -Wno-uninitialized -Wno-maybe-uninitialized -Wno-float-equal -Wno-conversion @@ -195,7 +196,6 @@ integration_test_SOURCES = \ test/integration/test_node.c \ test/integration/test_role_management.c \ test/integration/test_server.c \ - test/integration/test_vfs.c \ test/integration/main.c integration_test_CFLAGS = $(AM_CFLAGS) -Wno-conversion integration_test_LDFLAGS = $(AM_LDFLAGS) -no-install diff --git a/include/dqlite.h b/include/dqlite.h index 4598a6c36..d0dbe153f 100644 --- a/include/dqlite.h +++ b/include/dqlite.h @@ -743,13 +743,6 @@ DQLITE_API int dqlite_vfs_num_pages(sqlite3_vfs *vfs, const char *filename, unsigned *n); -/** - * Return the number of alive database pages. - */ -DQLITE_API int dqlite_vfs_num_alive_pages(sqlite3_vfs *vfs, - const char *filename, - unsigned *n); - /** * This function is DEPRECATED and will be removed in a future major release. * diff --git a/src/dqlite.c b/src/dqlite.c index f38fd6d78..21263330a 100644 --- a/src/dqlite.c +++ b/src/dqlite.c @@ -52,35 +52,11 @@ int dqlite_vfs_snapshot(sqlite3_vfs *vfs, return VfsSnapshot(vfs, filename, data, n); } -int dqlite_vfs_snapshot_disk(sqlite3_vfs *vfs, - const char *filename, - struct dqlite_buffer bufs[], - unsigned n) -{ - int rv; - if (n != 2) { - return -1; - } - - rv = VfsDiskSnapshotDb(vfs, filename, &bufs[0]); - if (rv != 0) { - return rv; - } - - rv = VfsDiskSnapshotWal(vfs, filename, &bufs[1]); - return rv; -} - int dqlite_vfs_num_pages(sqlite3_vfs *vfs, const char *filename, unsigned *n) { return VfsDatabaseNumPages(vfs, filename, 0, n); } -int dqlite_vfs_num_alive_pages(sqlite3_vfs *vfs, const char *filename, unsigned *n) -{ - return VfsDatabaseNumPages(vfs, filename, 1, n); -} - int dqlite_vfs_shallow_snapshot(sqlite3_vfs *vfs, const char *filename, struct dqlite_buffer bufs[], diff --git a/src/fsm.c b/src/fsm.c index 39f8b2a1f..fcd3b3043 100644 --- a/src/fsm.c +++ b/src/fsm.c @@ -391,8 +391,8 @@ static int encodeDatabase(struct db *db, header.filename = db->filename; header.main_size = (n - 1) * (uint64_t)db->config->page_size; - // The database is checkpointed before writing it to disk. - // As such, wal_size is always 0. + /* The database is checkpointed before writing it to disk. As such, + * wal_size is always 0. */ header.wal_size = 0; vfs = sqlite3_vfs_find(db->config->name); @@ -478,7 +478,7 @@ static unsigned dbNumPages(struct db *db) uint32_t n; vfs = sqlite3_vfs_find(db->config->name); - rv = VfsDatabaseNumPages(vfs, db->filename, 1, &n); + rv = VfsDatabaseNumPages(vfs, db->filename, true, &n); assert(rv == 0); return n; } diff --git a/src/vfs.c b/src/vfs.c index e397c78c2..d261ada79 100644 --- a/src/vfs.c +++ b/src/vfs.c @@ -1228,16 +1228,16 @@ static int vfsFileCheckReservedLock(sqlite3_file *file, int *result) /* Handle pragma a pragma file control. See the xFileControl * docstring in sqlite.h.in for more details. */ -static int vfsFileControlPragma(struct vfsFile *f, char **fnctl) +static int vfsFileControlPragma(struct vfsFile *f, char **fcntl) { const char *left; const char *right; assert(f != NULL); - assert(fnctl != NULL); + assert(fcntl != NULL); - left = fnctl[1]; - right = fnctl[2]; + left = fcntl[1]; + right = fcntl[2]; assert(left != NULL); @@ -1261,7 +1261,7 @@ static int vfsFileControlPragma(struct vfsFile *f, char **fnctl) if (f->database->n_pages > 0 && page_size != (int)vfsDatabaseGetPageSize(f->database)) { - fnctl[0] = sqlite3_mprintf( + fcntl[0] = sqlite3_mprintf( "changing page size is not supported"); return SQLITE_IOERR; } @@ -1270,13 +1270,13 @@ static int vfsFileControlPragma(struct vfsFile *f, char **fnctl) /* When the user executes 'PRAGMA journal_mode=x' we ensure * that the desired mode is 'wal'. */ if (strcasecmp(right, "wal") != 0) { - fnctl[0] = + fcntl[0] = sqlite3_mprintf("only WAL mode is supported"); return SQLITE_IOERR; } } else if (sqlite3_stricmp(left, "wal_checkpoint") == 0 || (sqlite3_stricmp(left, "wal_autocheckpoint") == 0 && right)) { - fnctl[0] = sqlite3_mprintf("custom checkpoint not allowed"); + fcntl[0] = sqlite3_mprintf("custom checkpoint not allowed"); return SQLITE_IOERR; } @@ -2453,30 +2453,34 @@ static uint32_t vfsDatabaseGetNumberOfPages(struct vfsDatabase *d) return ByteGetBe32(&page[28]); } -static uint32_t vfsDatabaseNumPages(struct vfsDatabase *database, int useWal) { +static uint32_t vfsDatabaseNumPages(struct vfsDatabase *database, bool use_wal) { uint32_t n; - if (useWal && database->wal.n_frames) { + if (use_wal && database->wal.n_frames > 0) { n = vfsFrameGetDatabaseSize(database->wal.frames[database->wal.n_frames-1]); - // If the result is zero, it means that the WAL contains uncommitted transactions. - assert(n != 0); + /* If the result is zero, it means that the WAL contains + * uncommitted transactions. */ + POST(n > 0); } else { n = vfsDatabaseGetNumberOfPages(database); } return n; } -int VfsDatabaseNumPages(sqlite3_vfs *vfs, const char *filename, int useWal, uint32_t *n) +int VfsDatabaseNumPages(sqlite3_vfs *vfs, + const char *filename, + bool use_wal, + uint32_t *n) { struct vfs *v; struct vfsDatabase *d; - + v = (struct vfs *)(vfs->pAppData); d = vfsDatabaseLookup(v, filename); if (d == NULL) { return -1; } - *n = vfsDatabaseNumPages(d, useWal); + *n = vfsDatabaseNumPages(d, use_wal); return 0; } @@ -2581,7 +2585,8 @@ static void vfsDatabaseShallowSnapshot(struct vfsDatabase *d, } static void vfsWalShallowSnapshot(struct vfsWal *w, - struct dqlite_buffer *bufs, uint32_t n) { + struct dqlite_buffer *bufs, + uint32_t n) { uint32_t page_size; unsigned i; @@ -2596,8 +2601,8 @@ static void vfsWalShallowSnapshot(struct vfsWal *w, struct vfsFrame *frame = w->frames[i]; uint32_t page_number = vfsFrameGetPageNumber(frame); assert(page_number <= n); - bufs[page_number-1].base = frame->page; - bufs[page_number-1].len = page_size; + bufs[page_number - 1].base = frame->page; + bufs[page_number - 1].len = page_size; } } @@ -2623,15 +2628,13 @@ int VfsShallowSnapshot(sqlite3_vfs *vfs, return SQLITE_CORRUPT; } - if (vfsDatabaseNumPages(database, 1) != n) { + if (vfsDatabaseNumPages(database, true) != n) { tracef("not enough buffers provided"); return SQLITE_MISUSE; } - /* Copy page pointers to first n buffers */ vfsDatabaseShallowSnapshot(database, bufs, n); - - /* Copy page pointers from wal */ + /* Update the array of pages by */ vfsWalShallowSnapshot(&database->wal, bufs, n); return 0; @@ -3053,17 +3056,17 @@ static int vfsDiskFileCheckReservedLock(sqlite3_file *file, int *result) /* Handle pragma a pragma file control. See the xFileControl * docstring in sqlite.h.in for more details. */ -static int vfsDiskFileControlPragma(struct vfsFile *f, char **fnctl) +static int vfsDiskFileControlPragma(struct vfsFile *f, char **fcntl) { int rv; const char *left; const char *right; assert(f != NULL); - assert(fnctl != NULL); + assert(fcntl != NULL); - left = fnctl[1]; - right = fnctl[2]; + left = fcntl[1]; + right = fcntl[2]; assert(left != NULL); @@ -3074,22 +3077,22 @@ static int vfsDiskFileControlPragma(struct vfsFile *f, char **fnctl) * Only used for on-disk databases. * */ if (f->db == NULL) { - fnctl[0] = sqlite3_mprintf("no DB file found"); + fcntl[0] = sqlite3_mprintf("no DB file found"); return SQLITE_IOERR; } if (page_size > UINT16_MAX) { - fnctl[0] = sqlite3_mprintf("max page_size exceeded"); + fcntl[0] = sqlite3_mprintf("max page_size exceeded"); return SQLITE_IOERR; } if (f->database->page_size == 0) { rv = f->db->pMethods->xFileControl( - f->db, SQLITE_FCNTL_PRAGMA, fnctl); + f->db, SQLITE_FCNTL_PRAGMA, fcntl); if (rv == SQLITE_NOTFOUND || rv == SQLITE_OK) { f->database->page_size = (uint16_t)page_size; } return rv; } else if ((uint16_t)page_size != f->database->page_size) { - fnctl[0] = sqlite3_mprintf( + fcntl[0] = sqlite3_mprintf( "changing page size is not supported"); return SQLITE_IOERR; } @@ -3097,7 +3100,7 @@ static int vfsDiskFileControlPragma(struct vfsFile *f, char **fnctl) /* When the user executes 'PRAGMA journal_mode=x' we ensure * that the desired mode is 'wal'. */ if (strcasecmp(right, "wal") != 0) { - fnctl[0] = + fcntl[0] = sqlite3_mprintf("only WAL mode is supported"); return SQLITE_IOERR; } @@ -3515,6 +3518,25 @@ int VfsDiskSnapshotDb(sqlite3_vfs *vfs, return rv; } +int VfsSnapshotDisk(sqlite3_vfs *vfs, + const char *filename, + struct dqlite_buffer bufs[], + unsigned n) +{ + int rv; + if (n != 2) { + return -1; + } + + rv = VfsDiskSnapshotDb(vfs, filename, &bufs[0]); + if (rv != 0) { + return rv; + } + + rv = VfsDiskSnapshotWal(vfs, filename, &bufs[1]); + return rv; +} + static int vfsDiskDatabaseRestore(struct vfsDatabase *d, const char *filename, const uint8_t *data, diff --git a/src/vfs.h b/src/vfs.h index 12d3150b0..61e973bb0 100644 --- a/src/vfs.h +++ b/src/vfs.h @@ -38,11 +38,15 @@ int VfsAbort(sqlite3_vfs *vfs, const char *filename); /* Make a full snapshot of a database. */ int VfsSnapshot(sqlite3_vfs *vfs, const char *filename, void **data, size_t *n); -/* Makes a full, shallow snapshot of a database. The first n-1 buffers will each - * contain a pointer to the actual database pages, while the n'th buffer - * will contain a copy of the WAL. `bufs` MUST point to an array of n - * `dqlite_buffer` structs and n MUST equal 1 + the number of pages in - * the database. */ +/** + * Prepare a snapshot of the selected database, borrowing from the in-memory + * state of the VFS. + * + * The provided array of buffers will be populated with pointers to the + * in-memory database held by the VFS. It's forbidden to checkpoint the + * database while these pointers are still in use. VfsDatabaseNumPages (with + * `use_wal = true`) should be used to determine how many buffers are needed. + */ int VfsShallowSnapshot(sqlite3_vfs *vfs, const char *filename, struct dqlite_buffer bufs[], @@ -58,6 +62,11 @@ int VfsDiskSnapshotDb(sqlite3_vfs *vfs, const char *path, struct dqlite_buffer *buf); +int VfsSnapshotDisk(sqlite3_vfs *vfs, + const char *filename, + struct dqlite_buffer bufs[], + uint32_t n); + /* Restore a database snapshot. */ int VfsRestore(sqlite3_vfs *vfs, const char *filename, @@ -71,8 +80,16 @@ int VfsDiskRestore(sqlite3_vfs *vfs, size_t main_size, size_t wal_size); -/* Number of pages in the database. */ -int VfsDatabaseNumPages(sqlite3_vfs *vfs, const char *filename, int useWal, uint32_t *n); +/** + * Number of pages in the database. + * + * If `use_wal` is set, returns the number of pages that the database would have + * after fully checkpointing the WAL. + */ +int VfsDatabaseNumPages(sqlite3_vfs *vfs, + const char *filename, + bool use_wal, + uint32_t *n); /* Returns the resulting size of the main file, wal file and n additional WAL * frames with the specified page_size. */ diff --git a/test/integration/test_vfs.c b/test/unit/test_vfs_extra.c similarity index 88% rename from test/integration/test_vfs.c rename to test/unit/test_vfs_extra.c index 0f47b1061..9136e37ee 100644 --- a/test/integration/test_vfs.c +++ b/test/unit/test_vfs_extra.c @@ -8,10 +8,11 @@ #include "../../include/dqlite.h" #include "../../src/raft.h" #include "../../src/lib/byte.h" +#include "../../src/vfs.h" #include -SUITE(vfs); +SUITE(vfs_extra); #define N_VFS 2 @@ -43,7 +44,7 @@ static void *setUp(const MunitParameter params[], void *user_data) for (i = 0; i < N_VFS; i++) { f->dirs[i] = NULL; sprintf(f->names[i], "%u", i + 1); - rv = dqlite_vfs_init(&f->vfs[i], f->names[i]); + rv = VfsInit(&f->vfs[i], f->names[i]); munit_assert_int(rv, ==, 0); const char *disk_mode_param = munit_parameters_get(params, "disk_mode"); @@ -51,7 +52,7 @@ static void *setUp(const MunitParameter params[], void *user_data) bool disk_mode = (bool)atoi(disk_mode_param); if (disk_mode) { f->dirs[i] = test_dir_setup(); - rv = dqlite_vfs_enable_disk(&f->vfs[i]); + rv = VfsEnableDisk(&f->vfs[i]); munit_assert_int(rv, ==, 0); } } @@ -71,7 +72,7 @@ static void tearDown(void *data) for (i = 0; i < N_VFS; i++) { rv = sqlite3_vfs_unregister(&f->vfs[i]); munit_assert_int(rv, ==, 0); - dqlite_vfs_close(&f->vfs[i]); + VfsClose(&f->vfs[i]); test_dir_tear_down(f->dirs[i]); } @@ -212,7 +213,7 @@ struct tx char path[VFS_PATH_SZ]; \ struct fixture *f = data; \ vfsFillDbPath(f, VFS, "test.db", path); \ - _rv = dqlite_vfs_poll(vfs, path, &_frames, &TX.n); \ + _rv = VfsPoll(vfs, path, &_frames, &TX.n); \ munit_assert_int(_rv, ==, 0); \ if (_frames != NULL) { \ TX.page_numbers = \ @@ -237,8 +238,7 @@ struct tx char path[VFS_PATH_SZ]; \ struct fixture *f = data; \ vfsFillDbPath(f, VFS, "test.db", path); \ - _rv = dqlite_vfs_apply(vfs, path, TX.n, TX.page_numbers, \ - TX.frames); \ + _rv = VfsApply(vfs, path, TX.n, TX.page_numbers, TX.frames); \ munit_assert_int(_rv, ==, 0); \ } while (0) @@ -250,7 +250,7 @@ struct tx char path[VFS_PATH_SZ]; \ struct fixture *f = data; \ vfsFillDbPath(f, VFS, "test.db", path); \ - _rv = dqlite_vfs_abort(vfs, path); \ + _rv = VfsAbort(vfs, path); \ munit_assert_int(_rv, ==, 0); \ } while (0) @@ -345,7 +345,7 @@ static struct dqlite_buffer n_bufs_to_buf(struct dqlite_buffer bufs[], char path[VFS_PATH_SZ]; \ struct fixture *f = data; \ vfsFillDbPath(f, VFS, "test.db", path); \ - _rv = dqlite_vfs_snapshot_disk(vfs, path, _bufs, _n); \ + _rv = VfsSnapshotDisk(vfs, path, _bufs, _n); \ munit_assert_int(_rv, ==, 0); \ _all_data = n_bufs_to_buf(_bufs, _n); \ /* Free WAL buffer after copy. */ \ @@ -363,8 +363,8 @@ static struct dqlite_buffer n_bufs_to_buf(struct dqlite_buffer bufs[], do { \ sqlite3_vfs *vfs = sqlite3_vfs_find(VFS); \ int _rv; \ - _rv = dqlite_vfs_snapshot(vfs, "test.db", &SNAPSHOT.data, \ - &SNAPSHOT.n); \ + _rv = VfsSnapshot(vfs, "test.db", \ + &SNAPSHOT.data, &SNAPSHOT.n); \ munit_assert_int(_rv, ==, 0); \ } while (0) @@ -377,11 +377,11 @@ static struct dqlite_buffer n_bufs_to_buf(struct dqlite_buffer bufs[], unsigned _n_pages; \ struct dqlite_buffer *_bufs; \ struct dqlite_buffer _all_data; \ - _rv = dqlite_vfs_num_alive_pages(vfs, "test.db", &_n_pages); \ + _rv = VfsDatabaseNumPages(vfs, "test.db", true, &_n_pages); \ munit_assert_int(_rv, ==, 0); \ _n = _n_pages; \ _bufs = sqlite3_malloc64(_n * sizeof(*_bufs)); \ - _rv = dqlite_vfs_shallow_snapshot(vfs, "test.db", _bufs, _n); \ + _rv = VfsShallowSnapshot(vfs, "test.db", _bufs, _n); \ munit_assert_int(_rv, ==, 0); \ _all_data = n_bufs_to_buf(_bufs, _n); \ sqlite3_free(_bufs); \ @@ -404,8 +404,9 @@ static struct dqlite_buffer n_bufs_to_buf(struct dqlite_buffer bufs[], } \ if (_shallow && !_disk_mode) { \ SNAPSHOT_SHALLOW(VFS, SNAPSHOT); \ - } else if (_shallow && _disk_mode) { \ - return MUNIT_SKIP; \ + } else if (_shallow && _disk_mode) { \ + /* Disk mode doesn't have shallow snapshots. */ \ + return MUNIT_SKIP; \ } else if (!_shallow && !_disk_mode) { \ SNAPSHOT_DEEP(VFS, SNAPSHOT); \ } else { \ @@ -427,18 +428,18 @@ static struct dqlite_buffer n_bufs_to_buf(struct dqlite_buffer bufs[], struct fixture *f = data; \ vfsFillDbPath(f, VFS, "test.db", path); \ if (_disk_mode) { \ - _rv = dqlite_vfs_restore_disk( \ - vfs, path, SNAPSHOT.data, SNAPSHOT.main_size, \ + _rv = VfsDiskRestore(vfs, path, \ + SNAPSHOT.data, SNAPSHOT.main_size, \ SNAPSHOT.wal_size); \ } else { \ - _rv = dqlite_vfs_restore(vfs, path, SNAPSHOT.data, \ - SNAPSHOT.n); \ + _rv = VfsRestore(vfs, path, \ + SNAPSHOT.data, SNAPSHOT.n); \ } \ munit_assert_int(_rv, ==, 0); \ } while (0) /* Open and close a new connection using the dqlite VFS. */ -TEST(vfs, open, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, open, setUp, tearDown, 0, vfs_params) { sqlite3 *db; OPEN("1", db); @@ -449,7 +450,7 @@ TEST(vfs, open, setUp, tearDown, 0, vfs_params) /* New frames appended to the WAL file by a sqlite3_step() call that has * triggered a write transactions are not immediately visible to other * connections after sqlite3_step() has returned. */ -TEST(vfs, writeTransactionNotImmediatelyVisible, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, writeTransactionNotImmediatelyVisible, setUp, tearDown, 0, vfs_params) { sqlite3 *db1; sqlite3 *db2; @@ -470,9 +471,9 @@ TEST(vfs, writeTransactionNotImmediatelyVisible, setUp, tearDown, 0, vfs_params) return MUNIT_OK; } -/* Invoking dqlite_vfs_poll() after a call to sqlite3_step() has triggered a +/* Invoking VfsPoll() after a call to sqlite3_step() has triggered a * write transaction returns the newly appended WAL frames. */ -TEST(vfs, pollAfterWriteTransaction, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, pollAfterWriteTransaction, setUp, tearDown, 0, vfs_params) { sqlite3 *db; sqlite3_stmt *stmt; @@ -500,11 +501,11 @@ TEST(vfs, pollAfterWriteTransaction, setUp, tearDown, 0, vfs_params) return MUNIT_OK; } -/* Invoking dqlite_vfs_poll() after a call to sqlite3_step() has triggered a +/* Invoking VfsPoll() after a call to sqlite3_step() has triggered a * write transaction sets a write lock on the WAL, so calls to sqlite3_step() * from other connections return SQLITE_BUSY if they try to start a write * transaction. */ -TEST(vfs, pollAcquireWriteLock, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, pollAcquireWriteLock, setUp, tearDown, 0, vfs_params) { sqlite3 *db1; sqlite3 *db2; @@ -538,8 +539,8 @@ TEST(vfs, pollAcquireWriteLock, setUp, tearDown, 0, vfs_params) /* If the page cache limit is exceeded during a call to sqlite3_step() that has * triggered a write transaction, some WAL frames will be written and then * overwritten before the final commit. Only the final version of the frame is - * included in the set returned by dqlite_vfs_poll(). */ -TEST(vfs, pollAfterPageStress, setUp, tearDown, 0, vfs_params) + * included in the set returned by VfsPoll(). */ +TEST(vfs_extra, pollAfterPageStress, setUp, tearDown, 0, vfs_params) { sqlite3 *db; sqlite3_stmt *stmt; @@ -601,7 +602,7 @@ TEST(vfs, pollAfterPageStress, setUp, tearDown, 0, vfs_params) /* Set the SQLite PENDING_BYTE at the start of the second page and make sure * all data entry is successful. */ -TEST(vfs, adaptPendingByte, setUp, tearDownRestorePendingByte, 0, vfs_params) +TEST(vfs_extra, adaptPendingByte, setUp, tearDownRestorePendingByte, 0, vfs_params) { sqlite3 *db; sqlite3_stmt *stmt; @@ -651,10 +652,10 @@ TEST(vfs, adaptPendingByte, setUp, tearDownRestorePendingByte, 0, vfs_params) return MUNIT_OK; } -/* Use dqlite_vfs_apply() to actually modify the WAL after a write transaction +/* Use VfsApply() to actually modify the WAL after a write transaction * was triggered by a call to sqlite3_step(), then perform a read transaction * and check that it can see the transaction changes. */ -TEST(vfs, applyMakesTransactionVisible, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, applyMakesTransactionVisible, setUp, tearDown, 0, vfs_params) { sqlite3 *db; sqlite3_stmt *stmt; @@ -677,10 +678,10 @@ TEST(vfs, applyMakesTransactionVisible, setUp, tearDown, 0, vfs_params) return MUNIT_OK; } -/* Use dqlite_vfs_apply() to actually modify the WAL after a write transaction +/* Use VfsApply() to actually modify the WAL after a write transaction * was triggered by an explicit "COMMIT" statement and check that changes are * visible. */ -TEST(vfs, applyExplicitTransaction, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, applyExplicitTransaction, setUp, tearDown, 0, vfs_params) { sqlite3 *db; sqlite3_stmt *stmt; @@ -718,9 +719,9 @@ TEST(vfs, applyExplicitTransaction, setUp, tearDown, 0, vfs_params) } /* Perform two consecutive full write transactions using sqlite3_step(), - * dqlite_vfs_poll() and dqlite_vfs_apply(), then run a read transaction and + * VfsPoll() and VfsApply(), then run a read transaction and * check that it can see all committed changes. */ -TEST(vfs, consecutiveWriteTransactions, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, consecutiveWriteTransactions, setUp, tearDown, 0, vfs_params) { sqlite3 *db; sqlite3_stmt *stmt; @@ -755,7 +756,7 @@ TEST(vfs, consecutiveWriteTransactions, setUp, tearDown, 0, vfs_params) /* Perform three consecutive write transactions, then re-open the database and * finally run a read transaction and check that it can see all committed * changes. */ -TEST(vfs, +TEST(vfs_extra, reopenAfterConsecutiveWriteTransactions, setUp, tearDown, @@ -796,10 +797,10 @@ TEST(vfs, return MUNIT_OK; } -/* Use dqlite_vfs_apply() to actually modify the WAL after a write transaction +/* Use VfsApply() to actually modify the WAL after a write transaction * was triggered by sqlite3_step(), and verify that the transaction is visible * from another existing connection. */ -TEST(vfs, +TEST(vfs_extra, transactionIsVisibleFromExistingConnection, setUp, tearDown, @@ -830,10 +831,10 @@ TEST(vfs, return MUNIT_OK; } -/* Use dqlite_vfs_apply() to actually modify the WAL after a write transaction +/* Use VfsApply() to actually modify the WAL after a write transaction * was triggered by sqlite3_step(), and verify that the transaction is visible * from a brand new connection. */ -TEST(vfs, transactionIsVisibleFromNewConnection, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, transactionIsVisibleFromNewConnection, setUp, tearDown, 0, vfs_params) { sqlite3 *db1; sqlite3 *db2; @@ -860,11 +861,11 @@ TEST(vfs, transactionIsVisibleFromNewConnection, setUp, tearDown, 0, vfs_params) return MUNIT_OK; } -/* Use dqlite_vfs_apply() to actually modify the WAL after a write transaction +/* Use VfsApply() to actually modify the WAL after a write transaction * was triggered by sqlite3_step(), then close the connection and open a new * one. A read transaction started in the new connection can see the changes * committed by the first one. */ -TEST(vfs, +TEST(vfs_extra, transactionIsVisibleFromReopenedConnection, setUp, tearDown, @@ -894,11 +895,11 @@ TEST(vfs, return MUNIT_OK; } -/* Use dqlite_vfs_apply() to replicate the very first write transaction on a +/* Use VfsApply() to replicate the very first write transaction on a * different VFS than the one that initially generated it. In that case it's * necessary to initialize the database file on the other VFS by opening and * closing a connection. */ -TEST(vfs, firstApplyOnDifferentVfs, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, firstApplyOnDifferentVfs, setUp, tearDown, 0, vfs_params) { sqlite3 *db1; sqlite3 *db2; @@ -926,10 +927,10 @@ TEST(vfs, firstApplyOnDifferentVfs, setUp, tearDown, 0, vfs_params) return MUNIT_OK; } -/* Use dqlite_vfs_apply() to replicate a second write transaction on a different +/* Use VfsApply() to replicate a second write transaction on a different * VFS than the one that initially generated it. In that case it's not necessary - * to do anything special before calling dqlite_vfs_apply(). */ -TEST(vfs, secondApplyOnDifferentVfs, setUp, tearDown, 0, vfs_params) + * to do anything special before calling VfsApply(). */ +TEST(vfs_extra, secondApplyOnDifferentVfs, setUp, tearDown, 0, vfs_params) { sqlite3 *db1; sqlite3 *db2; @@ -961,10 +962,10 @@ TEST(vfs, secondApplyOnDifferentVfs, setUp, tearDown, 0, vfs_params) return MUNIT_OK; } -/* Use dqlite_vfs_apply() to replicate a second write transaction on a different +/* Use VfsApply() to replicate a second write transaction on a different * VFS than the one that initially generated it and that has an open connection * which has built the WAL index header by preparing a statement. */ -TEST(vfs, applyOnDifferentVfsWithOpenConnection, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, applyOnDifferentVfsWithOpenConnection, setUp, tearDown, 0, vfs_params) { sqlite3 *db1; sqlite3 *db2; @@ -1009,7 +1010,7 @@ TEST(vfs, applyOnDifferentVfsWithOpenConnection, setUp, tearDown, 0, vfs_params) /* A write transaction that gets replicated to a different VFS is visible to a * new connection opened on that VFS. */ -TEST(vfs, transactionVisibleOnDifferentVfs, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, transactionVisibleOnDifferentVfs, setUp, tearDown, 0, vfs_params) { sqlite3 *db1; sqlite3 *db2; @@ -1038,9 +1039,9 @@ TEST(vfs, transactionVisibleOnDifferentVfs, setUp, tearDown, 0, vfs_params) return MUNIT_OK; } -/* Calling dqlite_vfs_abort() to cancel a transaction releases the write +/* Calling VfsAbort() to cancel a transaction releases the write * lock on the WAL. */ -TEST(vfs, abort, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, abort, setUp, tearDown, 0, vfs_params) { sqlite3 *db1; sqlite3 *db2; @@ -1074,7 +1075,7 @@ TEST(vfs, abort, setUp, tearDown, 0, vfs_params) /* Perform a checkpoint after a write transaction has completed, then perform * another write transaction and check that changes both before and after the * checkpoint are visible. */ -TEST(vfs, checkpoint, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, checkpoint, setUp, tearDown, 0, vfs_params) { sqlite3 *db1; sqlite3 *db2; @@ -1115,7 +1116,7 @@ TEST(vfs, checkpoint, setUp, tearDown, 0, vfs_params) } /* Replicate a write transaction that happens after a checkpoint. */ -TEST(vfs, applyOnDifferentVfsAfterCheckpoint, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, applyOnDifferentVfsAfterCheckpoint, setUp, tearDown, 0, vfs_params) { sqlite3 *db; sqlite3_stmt *stmt; @@ -1171,7 +1172,7 @@ TEST(vfs, applyOnDifferentVfsAfterCheckpoint, setUp, tearDown, 0, vfs_params) /* Replicate a write transaction that happens after a checkpoint, without * performing the checkpoint on the replicated DB. */ -TEST(vfs, +TEST(vfs_extra, applyOnDifferentVfsAfterCheckpointOtherVfsNoCheckpoint, setUp, tearDown, @@ -1246,7 +1247,7 @@ TEST(vfs, /* Replicate a write transaction that happens before a checkpoint, and is * replicated on a DB that has been checkpointed. */ -TEST(vfs, +TEST(vfs_extra, applyOnDifferentVfsExtraCheckpointsOnOtherVfs, setUp, tearDown, @@ -1322,7 +1323,7 @@ TEST(vfs, /* Replicate to another VFS a series of changes including a checkpoint, then * perform a new write transaction on that other VFS. */ -TEST(vfs, checkpointThenPerformTransaction, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, checkpointThenPerformTransaction, setUp, tearDown, 0, vfs_params) { sqlite3 *db1; struct tx tx1; @@ -1371,7 +1372,7 @@ TEST(vfs, checkpointThenPerformTransaction, setUp, tearDown, 0, vfs_params) /* Rollback a transaction that didn't hit the page cache limit and hence didn't * perform any pre-commit WAL writes. */ -TEST(vfs, rollbackTransactionWithoutPageStress, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, rollbackTransactionWithoutPageStress, setUp, tearDown, 0, vfs_params) { sqlite3 *db; struct tx tx; @@ -1411,7 +1412,7 @@ TEST(vfs, rollbackTransactionWithoutPageStress, setUp, tearDown, 0, vfs_params) /* Rollback a transaction that hit the page cache limit and hence performed some * pre-commit WAL writes. */ -TEST(vfs, rollbackTransactionWithPageStress, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, rollbackTransactionWithPageStress, setUp, tearDown, 0, vfs_params) { sqlite3 *db; sqlite3_stmt *stmt; @@ -1458,7 +1459,7 @@ TEST(vfs, rollbackTransactionWithPageStress, setUp, tearDown, 0, vfs_params) /* Try and fail to checkpoint a WAL that performed some pre-commit WAL writes. */ -TEST(vfs, checkpointTransactionWithPageStress, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, checkpointTransactionWithPageStress, setUp, tearDown, 0, vfs_params) { sqlite3 *db; struct tx tx; @@ -1490,7 +1491,7 @@ TEST(vfs, checkpointTransactionWithPageStress, setUp, tearDown, 0, vfs_params) /* A snapshot of a brand new database that has been just initialized contains * just the first page of the main database file. */ -TEST(vfs, snapshotInitialDatabase, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, snapshotInitialDatabase, setUp, tearDown, 0, vfs_params) { sqlite3 *db; struct snapshot snapshot; @@ -1514,17 +1515,18 @@ TEST(vfs, snapshotInitialDatabase, setUp, tearDown, 0, vfs_params) return MUNIT_OK; } -/* A snapshot of a database after the first write transaction gets applied - * contains the first page of the database plus the WAL file containing the - * transaction frames. */ -TEST(vfs, snapshotAfterFirstTransaction, setUp, tearDown, 0, vfs_params) +/* Take a snapshot of a database after the first write transaction has been + * applied. */ +TEST(vfs_extra, snapshotAfterFirstTransaction, setUp, tearDown, 0, vfs_params) { sqlite3 *db; struct snapshot snapshot; struct tx tx; uint8_t *page; - const uint32_t database_size = 1; - const uint32_t wal_size = 2; + uint32_t db_pages; + uint32_t wal_pages; + size_t total_size; + bool shallow = atoi(munit_parameters_get(params, SNAPSHOT_SHALLOW_PARAM)); OPEN("1", db); EXEC(db, "CREATE TABLE test(n INT)"); @@ -1540,13 +1542,22 @@ TEST(vfs, snapshotAfterFirstTransaction, setUp, tearDown, 0, vfs_params) page = snapshot.data; munit_assert_int(ByteGetBe16(&page[16]), ==, DB_PAGE_SIZE); - bool shallow = atoi(munit_parameters_get(params, SNAPSHOT_SHALLOW_PARAM)); + /* Page number 1 is written directly to the main file during the first + * write transaction. The WAL contains an updated version of page 1 + * and a new page 2. */ + db_pages = 1; + wal_pages = 2; if (shallow) { - munit_assert_int(snapshot.n, ==, DB_PAGE_SIZE * wal_size); - munit_assert_int(ByteGetBe32(&page[28]), ==, 2); + /* A shallow snapshot captures what the database would look + * like if we fully checkpointed the WAL. */ + munit_assert_int(snapshot.n, ==, DB_PAGE_SIZE * wal_pages); + munit_assert_int(ByteGetBe32(&page[28]), ==, wal_pages); } else { - munit_assert_int(snapshot.n, ==, database_size * DB_PAGE_SIZE + 32 + (24 + DB_PAGE_SIZE) * wal_size); - munit_assert_int(ByteGetBe32(&page[28]), ==, 1); + /* A deep snapshot captures the database and WAL separately, + * as they are. */ + total_size = db_pages * DB_PAGE_SIZE + 32 + (24 + DB_PAGE_SIZE) * wal_pages; + munit_assert_int(snapshot.n, ==, total_size); + munit_assert_int(ByteGetBe32(&page[28]), ==, db_pages); } raft_free(snapshot.data); @@ -1556,7 +1567,7 @@ TEST(vfs, snapshotAfterFirstTransaction, setUp, tearDown, 0, vfs_params) /* A snapshot of a database after a checkpoint contains all checkpointed pages * and no WAL frames. */ -TEST(vfs, snapshotAfterCheckpoint, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, snapshotAfterCheckpoint, setUp, tearDown, 0, vfs_params) { sqlite3 *db; struct snapshot snapshot; @@ -1591,7 +1602,7 @@ TEST(vfs, snapshotAfterCheckpoint, setUp, tearDown, 0, vfs_params) /* Restore a snapshot taken after a brand new database has been just * initialized. */ -TEST(vfs, restoreInitialDatabase, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, restoreInitialDatabase, setUp, tearDown, 0, vfs_params) { sqlite3 *db; struct snapshot snapshot; @@ -1613,7 +1624,7 @@ TEST(vfs, restoreInitialDatabase, setUp, tearDown, 0, vfs_params) /* Restore a snapshot of a database taken after the first write transaction gets * applied. */ -TEST(vfs, restoreAfterFirstTransaction, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, restoreAfterFirstTransaction, setUp, tearDown, 0, vfs_params) { sqlite3 *db; sqlite3_stmt *stmt; @@ -1650,7 +1661,7 @@ TEST(vfs, restoreAfterFirstTransaction, setUp, tearDown, 0, vfs_params) } /* Restore a snapshot of a database while a connection is open. */ -TEST(vfs, restoreWithOpenConnection, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, restoreWithOpenConnection, setUp, tearDown, 0, vfs_params) { sqlite3 *db; sqlite3_stmt *stmt; @@ -1684,7 +1695,7 @@ TEST(vfs, restoreWithOpenConnection, setUp, tearDown, 0, vfs_params) } /* Changing page_size to non-default value fails. */ -TEST(vfs, changePageSize, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, changePageSize, setUp, tearDown, 0, vfs_params) { sqlite3 *db; int rv; @@ -1700,7 +1711,7 @@ TEST(vfs, changePageSize, setUp, tearDown, 0, vfs_params) } /* Changing page_size to current value succeeds. */ -TEST(vfs, changePageSizeSameValue, setUp, tearDown, 0, vfs_params) +TEST(vfs_extra, changePageSizeSameValue, setUp, tearDown, 0, vfs_params) { sqlite3 *db; int rv;