diff --git a/options.go b/options.go new file mode 100644 index 0000000..3e6cb46 --- /dev/null +++ b/options.go @@ -0,0 +1,85 @@ +package sqkv + +import ( + "fmt" + "strings" +) + +// OpenMode for opening a DB +type OpenMode string + +const ( + OpenModeReadOnly OpenMode = "ro" + OpenModeReadWrite OpenMode = "rw" + OpenModeReadWriteCreate OpenMode = "rwc" + OpenModeMemory OpenMode = "memory" +) + +// OpenCache for setting the cache mode +type OpenCache string + +const ( + OpenCacheShared OpenCache = "shared" + OpenCachePrivate OpenCache = "private" +) + +// OpenJournalMode for setting the journal mode +type OpenJournalMode string + +const ( + OpenJournalModeDelete = "delete" + OpenJournalModeTruncate = "truncate" + OpenJournalModePersist = "persist" + OpenJournalModeMemory = "memory" + OpenJournalModeWAL = "wal" + OpenJournalModeOff = "off" +) + +// OpenConfig is a configuration for opening a DB +type OpenConfig struct { + Mode OpenMode + Cache OpenCache + JournalMode OpenJournalMode +} + +// String implements fmt.Stringer +func (oc OpenConfig) String() string { + var opts []string + if oc.Mode != "" { + opts = append(opts, "mode="+string(oc.Mode)) + } + if oc.Cache != "" { + opts = append(opts, "cache="+string(oc.Cache)) + } + if oc.JournalMode != "" { + opts = append(opts, fmt.Sprintf("_pragma=journal_mode(%s)", oc.JournalMode)) + } + if len(opts) == 0 { + return "" + } + return "?" + strings.Join(opts, "&") +} + +// OpenOption is a func for setting open config values +type OpenOption func(*OpenConfig) + +// WithMode sets the mode for opening the DB +func WithMode(mode OpenMode) OpenOption { + return func(oc *OpenConfig) { + oc.Mode = mode + } +} + +// WithCache sets the cache for opening the DB +func WithCache(cache OpenCache) OpenOption { + return func(oc *OpenConfig) { + oc.Cache = cache + } +} + +// WithJournalMode sets the journal mode for the DB +func WithJournalMode(journalMode OpenJournalMode) OpenOption { + return func(oc *OpenConfig) { + oc.JournalMode = journalMode + } +} diff --git a/sqkv.go b/sqkv.go index fb7e7c4..c8cdde8 100644 --- a/sqkv.go +++ b/sqkv.go @@ -1,6 +1,7 @@ package sqkv import ( + "context" "database/sql" "fmt" "regexp" @@ -14,8 +15,13 @@ type DB struct { } // Open opens (or creates) a new SQKV database -func Open(path string) (*DB, error) { - dsn := fmt.Sprintf("file:%s?cache=shared&mode=rwc", path) +func Open(path string, opts ...OpenOption) (*DB, error) { + cfg := &OpenConfig{} + for _, opt := range opts { + opt(cfg) + } + + dsn := fmt.Sprintf("file:%s%s", path, cfg) db, err := sql.Open("sqlite", dsn) if err != nil { return nil, err @@ -28,6 +34,16 @@ func Open(path string) (*DB, error) { return sqkv, err } +// Close closes the connection to the database +func (d *DB) Close() error { + return d.db.Close() +} + +// Conn returns a direct DB connection +func (d *DB) Conn() (*sql.Conn, error) { + return d.db.Conn(context.Background()) +} + // Get returns a value from the default bucket func (d *DB) Get(key string) (string, error) { b, err := d.Bucket(DefaultBucket) diff --git a/sqkv_test.go b/sqkv_test.go index 09a597e..c061d8f 100644 --- a/sqkv_test.go +++ b/sqkv_test.go @@ -2,6 +2,7 @@ package sqkv import ( "errors" + "os" "path/filepath" "testing" @@ -16,6 +17,7 @@ func TestDefault(t *testing.T) { db, err := Open(dbPath) assert.NoErr(err) // Should create database + defer db.Close() err = db.Put("foo", "bar") assert.NoErr(err) // Should put kv @@ -39,6 +41,7 @@ func TestBucket(t *testing.T) { db, err := Open(dbPath) assert.NoErr(err) // Should create database + defer db.Close() _, err = db.Bucket("honk") assert.True(errors.Is(err, ErrBucketDoesNotExist)) // Bucket does not exist; should error @@ -65,3 +68,29 @@ func TestBucket(t *testing.T) { _, err = b.Get("foo") assert.True(errors.Is(err, ErrKeyDoesNotExist)) } + +func TestOptions(t *testing.T) { + assert := is.New(t) + + tmp := t.TempDir() + dbPath := filepath.Join(tmp, "opt.db") + + _, err := Open(dbPath, WithMode(OpenModeReadOnly)) + assert.True(err != nil) // Database should not be created + + _, err = Open(dbPath, WithMode(OpenModeReadWrite)) + assert.True(err != nil) // Database should not be created + + _, err = Open(dbPath, WithMode(OpenModeMemory)) + assert.NoErr(err) // Database should not be created, but no error + _, err = os.Stat(dbPath) + assert.True(err != nil) // Database should not be created on disk + + db, err := Open(dbPath, WithJournalMode(OpenJournalModeWAL)) + defer db.Close() + + var jm string + row := db.db.QueryRow("PRAGMA journal_mode") + assert.NoErr(row.Scan(&jm)) // Should return PRAGMA result + assert.Equal(jm, "wal") // Journal mode should be WAL +}