1
0
Fork 0

Merge pull request #1314 from rqlite/enable-wal-simple

Enable WAL mode for on-disk mode
master
Philip O'Toole 1 year ago committed by GitHub
commit 0c68a671a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -137,6 +137,29 @@ func IsWALModeEnabled(b []byte) bool {
return len(b) >= 20 && b[18] == 2 && b[19] == 2 return len(b) >= 20 && b[18] == 2 && b[19] == 2
} }
// IsDELETEModeEnabledSQLiteFile checks that the supplied path looks like a SQLite
// with DELETE mode enabled.
func IsDELETEModeEnabledSQLiteFile(path string) bool {
f, err := os.Open(path)
if err != nil {
return false
}
defer f.Close()
b := make([]byte, 20)
if _, err := f.Read(b); err != nil {
return false
}
return IsDELETEModeEnabled(b)
}
// IsDELETEModeEnabledSQLiteFile checks that the supplied path looks like a SQLite
// with DELETE mode enabled.
func IsDELETEModeEnabled(b []byte) bool {
return len(b) >= 20 && b[18] == 1 && b[19] == 1
}
// RemoveFiles removes the SQLite database file, and any associated WAL and SHM files. // RemoveFiles removes the SQLite database file, and any associated WAL and SHM files.
func RemoveFiles(path string) error { func RemoveFiles(path string) error {
if err := os.Remove(path); err != nil && !os.IsNotExist(err) { if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
@ -168,10 +191,12 @@ func Open(dbPath string, fkEnabled, wal bool) (*DB, error) {
return nil, err return nil, err
} }
if wal { mode := "WAL"
if _, err := rwDB.Exec("PRAGMA journal_mode=WAL"); err != nil { if !wal {
return nil, err mode = "DELETE"
} }
if _, err := rwDB.Exec(fmt.Sprintf("PRAGMA journal_mode=%s", mode)); err != nil {
return nil, err
} }
roOpts := []string{ roOpts := []string{
@ -923,47 +948,73 @@ func (db *DB) Request(req *command.Request, xTime bool) ([]*command.ExecuteQuery
} }
// Backup writes a consistent snapshot of the database to the given file. // Backup writes a consistent snapshot of the database to the given file.
// This function can be called when changes to the database are in flight. // The resultant SQLite database file will be in DELETE mode. This function
// can be called when changes to the database are in flight.
func (db *DB) Backup(path string) error { func (db *DB) Backup(path string) error {
dstDB, err := Open(path, false, false) dstDB, err := Open(path, false, false)
if err != nil { if err != nil {
return err return err
} }
defer dstDB.Close()
if err := copyDatabase(dstDB, db); err != nil { if err := copyDatabase(dstDB, db); err != nil {
return fmt.Errorf("backup database: %s", err) return fmt.Errorf("backup database: %s", err)
} }
return nil
// Source database might be in WAL mode.
_, err = dstDB.ExecuteStringStmt("PRAGMA journal_mode=DELETE")
if err != nil {
return err
}
return dstDB.Close()
} }
// Copy copies the contents of the database to the given database. All other // Copy copies the contents of the database to the given database. All other
// attributes of the given database remain untouched e.g. whether it's an // attributes of the given database remain untouched e.g. whether it's an
// on-disk database. This function can be called when changes to the source // on-disk database, except the database will be placed in DELETE mode.
// database are in flight. // This function can be called when changes to the source database are in flight.
func (db *DB) Copy(dstDB *DB) error { func (db *DB) Copy(dstDB *DB) error {
if err := copyDatabase(dstDB, db); err != nil { if err := copyDatabase(dstDB, db); err != nil {
return fmt.Errorf("copy database: %s", err) return fmt.Errorf("copy database: %s", err)
} }
return nil _, err := dstDB.ExecuteStringStmt("PRAGMA journal_mode=DELETE")
return err
} }
// Serialize returns a byte slice representation of the SQLite database. For // Serialize returns a byte slice representation of the SQLite database. For
// an ordinary on-disk database file, the serialization is just a copy of the // an ordinary on-disk database file, the serialization is just a copy of the
// disk file. For an in-memory database or a "TEMP" database, the serialization // disk file. For an in-memory database or a "TEMP" database, the serialization
// is the same sequence of bytes which would be written to disk if that database // is the same sequence of bytes which would be written to disk if that database
// were backed up to disk. If the database is in WAL mode, a RESTART checkpoint // were backed up to disk. If the database is in WAL mode, a temporary on-disk
// will be performed before the database is serialized. This function must not // copy is made, and it is this copy that is serialized. This function must not
// be called while any writes are happening to the database. // be called while any writes are happening to the database.
func (db *DB) Serialize() ([]byte, error) { func (db *DB) Serialize() ([]byte, error) {
if !db.memory { if !db.memory {
// If the database is in WAL mode, perform a checkpoint before serializing.
if db.wal { if db.wal {
if err := db.Checkpoint(defaultCheckpointTimeout); err != nil { tmpFile, err := os.CreateTemp("", "rqlite-serialize")
if err != nil {
return nil, err return nil, err
} }
defer os.Remove(tmpFile.Name())
defer tmpFile.Close()
if err := db.Backup(tmpFile.Name()); err != nil {
return nil, err
}
newDB, err := Open(tmpFile.Name(), db.fkEnabled, false)
if err != nil {
return nil, err
}
defer newDB.Close()
return newDB.Serialize()
} }
// Simply read and return the SQLite file. // Simply read and return the SQLite file.
return os.ReadFile(db.path) b, err := os.ReadFile(db.path)
if err != nil {
return nil, err
}
return b, nil
} }
conn, err := db.roDB.Conn(context.Background()) conn, err := db.roDB.Conn(context.Background())

@ -962,6 +962,11 @@ func testSerialize(t *testing.T, db *DB) {
if err != nil { if err != nil {
t.Fatalf("failed to serialize database: %s", err.Error()) t.Fatalf("failed to serialize database: %s", err.Error())
} }
if !IsDELETEModeEnabled(b) {
t.Fatalf("expected DELETE mode to be enabled")
}
err = ioutil.WriteFile(dstDB.Name(), b, 0644) err = ioutil.WriteFile(dstDB.Name(), b, 0644)
if err != nil { if err != nil {
t.Fatalf("failed to write serialized database to file: %s", err.Error()) t.Fatalf("failed to write serialized database to file: %s", err.Error())
@ -1212,6 +1217,10 @@ func testCopy(t *testing.T, db *DB) {
t.Fatalf("failed to copy database: %s", err.Error()) t.Fatalf("failed to copy database: %s", err.Error())
} }
if !IsDELETEModeEnabledSQLiteFile(dstFile) {
t.Fatalf("Destination file not marked in DELETE mode")
}
ro, err := dstDB.QueryStringStmt(`SELECT * FROM foo`) ro, err := dstDB.QueryStringStmt(`SELECT * FROM foo`)
if err != nil { if err != nil {
t.Fatalf("failed to query table: %s", err.Error()) t.Fatalf("failed to query table: %s", err.Error())
@ -1256,6 +1265,9 @@ func testBackup(t *testing.T, db *DB) {
if err != nil { if err != nil {
t.Fatalf("failed to backup database: %s", err.Error()) t.Fatalf("failed to backup database: %s", err.Error())
} }
if !IsDELETEModeEnabledSQLiteFile(dstDB) {
t.Fatalf("Backup file not marked in DELETE mode")
}
newDB, err := Open(dstDB, false, false) newDB, err := Open(dstDB, false, false)
if err != nil { if err != nil {

@ -55,9 +55,13 @@ func Test_IsWALModeEnabledOnDiskDELETE(t *testing.T) {
t.Fatalf("failed to close database: %s", err.Error()) t.Fatalf("failed to close database: %s", err.Error())
} }
if !IsDELETEModeEnabledSQLiteFile(path) {
t.Fatalf("DELETE file marked as non-DELETE")
}
if IsWALModeEnabledSQLiteFile(path) { if IsWALModeEnabledSQLiteFile(path) {
t.Fatalf("non WAL file marked as WAL") t.Fatalf("non WAL file marked as WAL")
} }
data, err := os.ReadFile(path) data, err := os.ReadFile(path)
if err != nil { if err != nil {
t.Fatalf("failed to read SQLite file: %s", err.Error()) t.Fatalf("failed to read SQLite file: %s", err.Error())
@ -65,6 +69,9 @@ func Test_IsWALModeEnabledOnDiskDELETE(t *testing.T) {
if IsWALModeEnabled(data) { if IsWALModeEnabled(data) {
t.Fatalf("non WAL data marked as WAL") t.Fatalf("non WAL data marked as WAL")
} }
if !IsDELETEModeEnabled(data) {
t.Fatalf("data marked as non-DELETE")
}
} }
func Test_IsWALModeEnabledOnDiskWAL(t *testing.T) { func Test_IsWALModeEnabledOnDiskWAL(t *testing.T) {
@ -91,6 +98,10 @@ func Test_IsWALModeEnabledOnDiskWAL(t *testing.T) {
if !IsWALModeEnabledSQLiteFile(path) { if !IsWALModeEnabledSQLiteFile(path) {
t.Fatalf("WAL file marked as non-WAL") t.Fatalf("WAL file marked as non-WAL")
} }
if IsDELETEModeEnabledSQLiteFile(path) {
t.Fatalf("WAL file marked as DELETE")
}
data, err := os.ReadFile(path) data, err := os.ReadFile(path)
if err != nil { if err != nil {
t.Fatalf("failed to read SQLite file: %s", err.Error()) t.Fatalf("failed to read SQLite file: %s", err.Error())
@ -98,6 +109,9 @@ func Test_IsWALModeEnabledOnDiskWAL(t *testing.T) {
if !IsWALModeEnabled(data) { if !IsWALModeEnabled(data) {
t.Fatalf("WAL data marked as non-WAL") t.Fatalf("WAL data marked as non-WAL")
} }
if IsDELETEModeEnabled(data) {
t.Fatalf("WAL data marked as DELETE")
}
} }
// Add tests that check a WAL file is created, and that a checkpoint succeeds // Add tests that check a WAL file is created, and that a checkpoint succeeds

Loading…
Cancel
Save