From 6ceaa64c07486703827a1cc1bd9ee9fef75cd41b Mon Sep 17 00:00:00 2001 From: Anton Zhiyanov Date: Tue, 23 Jul 2024 02:22:09 +0500 Subject: [PATCH] feat: read-only mode (#31) Fixes #30. --- redka.go | 37 +++++++++++++++++++++++++++++++++++-- redka_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/redka.go b/redka.go index 5375f08..9f2cf56 100644 --- a/redka.go +++ b/redka.go @@ -58,6 +58,9 @@ type Options struct { Pragma map[string]string // Logger for the database. If nil, uses a silent logger. Logger *slog.Logger + + // If true, opens the database in read-only mode. + readonly bool } var defaultOptions = Options{ @@ -118,6 +121,24 @@ func Open(path string, opts *Options) (*DB, error) { return new(sdb, opts) } +// OpenRead opens an existing database at the given path in read-only mode. +func OpenRead(path string, opts *Options) (*DB, error) { + // Apply the default options if necessary. + opts = applyOptions(defaultOptions, opts) + opts.readonly = true + + // Open the read-only database handle. + dataSource := sqlx.DataSource(path, false, opts.Pragma) + db, err := sql.Open(opts.DriverName, dataSource) + if err != nil { + return nil, err + } + + // Create the database-backed repository. + sdb := sqlx.New(db, db, newTx) + return new(sdb, opts) +} + // OpenDB connects to an existing SQL database. // Creates the database schema if necessary. // The opts parameter is optional. If nil, uses default options. @@ -130,6 +151,14 @@ func OpenDB(rw *sql.DB, ro *sql.DB, opts *Options) (*DB, error) { return new(sdb, opts) } +// OpenReadDB connects to an existing SQL database in read-only mode. +func OpenReadDB(db *sql.DB, opts *Options) (*DB, error) { + opts = applyOptions(defaultOptions, opts) + opts.readonly = true + sdb := sqlx.New(db, db, newTx) + return new(sdb, opts) +} + // new creates a new database. func new(sdb *sqlx.DB[*Tx], opts *Options) (*DB, error) { rdb := &DB{ @@ -142,7 +171,9 @@ func new(sdb *sqlx.DB[*Tx], opts *Options) (*DB, error) { zsetDB: rzset.New(sdb.RW, sdb.RO), log: opts.Logger, } - rdb.bg = rdb.startBgManager() + if !opts.readonly { + rdb.bg = rdb.startBgManager() + } return rdb, nil } @@ -228,7 +259,9 @@ func (db *DB) ViewContext(ctx context.Context, f func(tx *Tx) error) error { // Close closes the database. // It's safe for concurrent use by multiple goroutines. func (db *DB) Close() error { - db.bg.Stop() + if db.bg != nil { + db.bg.Stop() + } var allErr error if err := db.RW.Close(); err != nil { allErr = err diff --git a/redka_test.go b/redka_test.go index 0507272..f8e1aec 100644 --- a/redka_test.go +++ b/redka_test.go @@ -20,6 +20,34 @@ func ExampleOpen() { // ... } +func ExampleOpenRead() { + // open a writable database + db, err := redka.Open("data.db", nil) + if err != nil { + panic(err) + } + db.Str().Set("name", "alice") + db.Close() + + // open a read-only database + db, err = redka.OpenRead("data.db", nil) + if err != nil { + panic(err) + } + // read operations work fine + name, _ := db.Str().Get("name") + fmt.Println(name) + // write operations will fail + err = db.Str().Set("name", "bob") + fmt.Println(err) + // attempt to write a readonly database + db.Close() + + // Output: + // alice + // attempt to write a readonly database +} + func ExampleDB_Close() { db, err := redka.Open("file:/data.db?vfs=memdb", nil) if err != nil {