From c22eea13adab71a48694ca276183809977aef892 Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Tue, 5 Jan 2021 14:07:17 -0700 Subject: [PATCH] Add checkpoint tests --- db.go | 19 +++++++++--------- db_test.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/db.go b/db.go index c08f541..8e248b5 100644 --- a/db.go +++ b/db.go @@ -34,13 +34,12 @@ const ( // DB represents a managed instance of a SQLite database in the file system. type DB struct { - mu sync.RWMutex - path string // part to database - db *sql.DB // target database - rtx *sql.Tx // long running read transaction - pageSize int // page size, in bytes - notify chan struct{} // closes on WAL change - lastCheckpointAt time.Time // last checkpoint time + mu sync.RWMutex + path string // part to database + db *sql.DB // target database + rtx *sql.Tx // long running read transaction + pageSize int // page size, in bytes + notify chan struct{} // closes on WAL change ctx context.Context cancel func() @@ -682,7 +681,7 @@ func (db *DB) Sync() (err error) { checkpoint = true } else if db.MaxCheckpointPageN > 0 && newWALSize >= calcWALSize(db.pageSize, db.MaxCheckpointPageN) { checkpoint, checkpointMode = true, CheckpointModeRestart - } else if db.CheckpointInterval > 0 && !db.lastCheckpointAt.IsZero() && time.Since(db.lastCheckpointAt) > db.CheckpointInterval && newWALSize > calcWALSize(db.pageSize, 1) { + } else if db.CheckpointInterval > 0 && !info.dbModTime.IsZero() && time.Since(info.dbModTime) > db.CheckpointInterval && newWALSize > calcWALSize(db.pageSize, 1) { checkpoint = true } @@ -750,6 +749,7 @@ func (db *DB) verify() (info syncInfo, err error) { if fi, err := os.Stat(db.Path()); err != nil { return info, err } else { + info.dbModTime = fi.ModTime() db.dbSizeGauge.Set(float64(fi.Size())) } @@ -826,6 +826,7 @@ func (db *DB) verify() (info syncInfo, err error) { type syncInfo struct { generation string // generation name + dbModTime time.Time // last modified date of real DB file walSize int64 // size of real WAL file walModTime time.Time // last modified date of real WAL file shadowWALPath string // name of last shadow WAL file @@ -1166,8 +1167,6 @@ func (db *DB) checkpoint(mode string) (err error) { return fmt.Errorf("release read lock: %w", err) } - db.lastCheckpointAt = time.Now() - return nil } diff --git a/db_test.go b/db_test.go index 688c413..8d50179 100644 --- a/db_test.go +++ b/db_test.go @@ -519,17 +519,64 @@ func TestDB_Sync(t *testing.T) { // Ensure DB checkpoints after minimum number of pages. t.Run("MinCheckpointPageN", func(t *testing.T) { - t.Skip() - }) + db, sqldb := MustOpenDBs(t) + defer MustCloseDBs(t, db, sqldb) - // Ensure DB forces checkpoint after maximum number of pages. - t.Run("MaxCheckpointPageN", func(t *testing.T) { - t.Skip() + // Execute a query to force a write to the WAL and then sync. + if _, err := sqldb.Exec(`CREATE TABLE foo (bar TEXT);`); err != nil { + t.Fatal(err) + } else if err := db.Sync(); err != nil { + t.Fatal(err) + } + + // Write at least minimum number of pages to trigger rollover. + for i := 0; i < db.MinCheckpointPageN; i++ { + if _, err := sqldb.Exec(`INSERT INTO foo (bar) VALUES ('baz');`); err != nil { + t.Fatal(err) + } + } + + // Sync to shadow WAL. + if err := db.Sync(); err != nil { + t.Fatal(err) + } + + // Ensure position is now on the second index. + if pos, err := db.Pos(); err != nil { + t.Fatal(err) + } else if got, want := pos.Index, 1; got != want { + t.Fatalf("Index=%v, want %v", got, want) + } }) // Ensure DB checkpoints after interval. t.Run("CheckpointInterval", func(t *testing.T) { - t.Skip() + db, sqldb := MustOpenDBs(t) + defer MustCloseDBs(t, db, sqldb) + + // Execute a query to force a write to the WAL and then sync. + if _, err := sqldb.Exec(`CREATE TABLE foo (bar TEXT);`); err != nil { + t.Fatal(err) + } else if err := db.Sync(); err != nil { + t.Fatal(err) + } + + // Reduce checkpoint interval to ensure a rollover is triggered. + db.CheckpointInterval = 1 * time.Nanosecond + + // Write to WAL & sync. + if _, err := sqldb.Exec(`INSERT INTO foo (bar) VALUES ('baz');`); err != nil { + t.Fatal(err) + } else if err := db.Sync(); err != nil { + t.Fatal(err) + } + + // Ensure position is now on the second index. + if pos, err := db.Pos(); err != nil { + t.Fatal(err) + } else if got, want := pos.Index, 1; got != want { + t.Fatalf("Index=%v, want %v", got, want) + } }) }