diff --git a/db.go b/db.go index 64735c61..2b6d9e39 100755 --- a/db.go +++ b/db.go @@ -18,9 +18,14 @@ type Range struct { // DB is a reusable handle to a RocksDB database on disk, created by Open. type DB struct { - c *C.rocksdb_t - name string - opts *Options + c *C.rocksdb_t + closer func(*C.rocksdb_t) + name string + opts *Options +} + +func dbClose(c *C.rocksdb_t) { + C.rocksdb_close(c) } // OpenDb opens a database with the specified options. @@ -36,9 +41,10 @@ func OpenDb(opts *Options, name string) (*DB, error) { return nil, errors.New(C.GoString(cErr)) } return &DB{ - name: name, - c: db, - opts: opts, + c: db, + closer: dbClose, + name: name, + opts: opts, }, nil } @@ -55,9 +61,10 @@ func OpenDbWithTTL(opts *Options, name string, ttl int) (*DB, error) { return nil, errors.New(C.GoString(cErr)) } return &DB{ - name: name, - c: db, - opts: opts, + c: db, + closer: dbClose, + name: name, + opts: opts, }, nil } @@ -74,9 +81,10 @@ func OpenDbForReadOnly(opts *Options, name string, errorIfLogFileExist bool) (*D return nil, errors.New(C.GoString(cErr)) } return &DB{ - name: name, - c: db, - opts: opts, + c: db, + closer: dbClose, + name: name, + opts: opts, }, nil } @@ -133,9 +141,10 @@ func OpenDbColumnFamilies( } return &DB{ - name: name, - c: db, - opts: opts, + c: db, + closer: dbClose, + name: name, + opts: opts, }, cfHandles, nil } @@ -195,9 +204,10 @@ func OpenDbForReadOnlyColumnFamilies( } return &DB{ - name: name, - c: db, - opts: opts, + c: db, + closer: dbClose, + name: name, + opts: opts, }, cfHandles, nil } @@ -904,7 +914,7 @@ func (db *DB) NewCheckpoint() (*Checkpoint, error) { // Close closes the database. func (db *DB) Close() { - C.rocksdb_close(db.c) + db.closer(db.c) } // DestroyDb removes a database entirely, removing everything from the diff --git a/options_transaction.go b/options_transaction.go index cb72bff8..d051592e 100644 --- a/options_transaction.go +++ b/options_transaction.go @@ -64,3 +64,31 @@ func (opts *TransactionOptions) Destroy() { C.rocksdb_transaction_options_destroy(opts.c) opts.c = nil } + +// OptimisticTransactionOptions represent all of the available options for +// a optimistic transaction on the database. +type OptimisticTransactionOptions struct { + c *C.rocksdb_optimistictransaction_options_t +} + +// NewDefaultOptimisticTransactionOptions creates a default OptimisticTransactionOptions object. +func NewDefaultOptimisticTransactionOptions() *OptimisticTransactionOptions { + return NewNativeOptimisticTransactionOptions(C.rocksdb_optimistictransaction_options_create()) +} + +// NewNativeOptimisticTransactionOptions creates a OptimisticTransactionOptions object. +func NewNativeOptimisticTransactionOptions(c *C.rocksdb_optimistictransaction_options_t) *OptimisticTransactionOptions { + return &OptimisticTransactionOptions{c} +} + +// Destroy deallocates the OptimisticTransactionOptions object. +func (opts *OptimisticTransactionOptions) Destroy() { + C.rocksdb_optimistictransaction_options_destroy(opts.c) + opts.c = nil +} + +// SetSetSnapshot to true is the same as calling +// Transaction::SetSnapshot(). +func (opts *OptimisticTransactionOptions) SetSetSnapshot(value bool) { + C.rocksdb_optimistictransaction_options_set_set_snapshot(opts.c, boolToChar(value)) +} diff --git a/transactiondb.go b/transactiondb.go index cfdeac9c..8820708b 100644 --- a/transactiondb.go +++ b/transactiondb.go @@ -141,3 +141,68 @@ func (transactionDB *TransactionDB) Close() { C.rocksdb_transactiondb_close(transactionDB.c) transactionDB.c = nil } + +// OptimisticTransactionDB is a reusable handle to a RocksDB optimistic transactional database on disk, created by OpenOptimisticTransactionDb. +type OptimisticTransactionDB struct { + c *C.rocksdb_optimistictransactiondb_t + name string + opts *Options +} + +// OpenTransactionDb opens a database with the specified options. +func OpenOptimisticTransactionDb( + opts *Options, + name string, +) (*OptimisticTransactionDB, error) { + var ( + cErr *C.char + cName = C.CString(name) + ) + defer C.free(unsafe.Pointer(cName)) + db := C.rocksdb_optimistictransactiondb_open(opts.c, cName, &cErr) + if cErr != nil { + defer C.rocksdb_free(unsafe.Pointer(cErr)) + return nil, errors.New(C.GoString(cErr)) + } + return &OptimisticTransactionDB{ + name: name, + c: db, + opts: opts, + }, nil +} + +// GetBaseDB returns the base database. +func (db *OptimisticTransactionDB) GetBaseDB() *DB { + return &DB{ + c: C.rocksdb_optimistictransactiondb_get_base_db(db.c), + closer: func(c *C.rocksdb_t) { C.rocksdb_optimistictransactiondb_close_base_db(c) }, + name: db.name, + opts: db.opts, + } +} + +// TransactionBegin begins a new transaction +// with the WriteOptions and TransactionOptions given. +func (db *OptimisticTransactionDB) TransactionBegin( + opts *WriteOptions, + transactionOpts *OptimisticTransactionOptions, + oldTransaction *Transaction, +) *Transaction { + if oldTransaction != nil { + return NewNativeTransaction(C.rocksdb_optimistictransaction_begin( + db.c, + opts.c, + transactionOpts.c, + oldTransaction.c, + )) + } + + return NewNativeTransaction(C.rocksdb_optimistictransaction_begin( + db.c, opts.c, transactionOpts.c, nil)) +} + +// Close closes the database. +func (transactionDB *OptimisticTransactionDB) Close() { + C.rocksdb_optimistictransactiondb_close(transactionDB.c) + transactionDB.c = nil +} diff --git a/transactiondb_test.go b/transactiondb_test.go index 48fb382b..0a038629 100644 --- a/transactiondb_test.go +++ b/transactiondb_test.go @@ -137,3 +137,104 @@ func newTestTransactionDB(t *testing.T, name string, applyOpts func(opts *Option return db } + +func TestOpenOptimisticTransactionDb(t *testing.T) { + db := newTestOptimisticTransactionDB(t, "TestOpenOptimisticTransactionDb", nil) + defer db.Close() +} + +func TestOptimisticTransactionDBCRUD(t *testing.T) { + txdb := newTestOptimisticTransactionDB(t, "TestOptimisticTransactionDBGet", nil) + defer txdb.Close() + + db := txdb.GetBaseDB() + defer db.Close() + + var ( + givenKey = []byte("hello") + givenVal1 = []byte("world1") + givenVal2 = []byte("world2") + givenTxnKey = []byte("hello2") + givenTxnKey2 = []byte("hello3") + givenTxnVal1 = []byte("whatawonderful") + wo = NewDefaultWriteOptions() + ro = NewDefaultReadOptions() + to = NewDefaultOptimisticTransactionOptions() + ) + + // create + ensure.Nil(t, db.Put(wo, givenKey, givenVal1)) + + // retrieve + v1, err := db.Get(ro, givenKey) + defer v1.Free() + ensure.Nil(t, err) + ensure.DeepEqual(t, v1.Data(), givenVal1) + + // update + ensure.Nil(t, db.Put(wo, givenKey, givenVal2)) + v2, err := db.Get(ro, givenKey) + defer v2.Free() + ensure.Nil(t, err) + ensure.DeepEqual(t, v2.Data(), givenVal2) + + // delete + ensure.Nil(t, db.Delete(wo, givenKey)) + v3, err := db.Get(ro, givenKey) + defer v3.Free() + ensure.Nil(t, err) + ensure.True(t, v3.Data() == nil) + + // transaction + txn := txdb.TransactionBegin(wo, to, nil) + defer txn.Destroy() + // create + ensure.Nil(t, txn.Put(givenTxnKey, givenTxnVal1)) + v4, err := txn.Get(ro, givenTxnKey) + defer v4.Free() + ensure.Nil(t, err) + ensure.DeepEqual(t, v4.Data(), givenTxnVal1) + + ensure.Nil(t, txn.Commit()) + v5, err := db.Get(ro, givenTxnKey) + defer v5.Free() + ensure.Nil(t, err) + ensure.DeepEqual(t, v5.Data(), givenTxnVal1) + + // transaction + txn2 := txdb.TransactionBegin(wo, to, nil) + defer txn2.Destroy() + // create + ensure.Nil(t, txn2.Put(givenTxnKey2, givenTxnVal1)) + // rollback + ensure.Nil(t, txn2.Rollback()) + + v6, err := txn2.Get(ro, givenTxnKey2) + defer v6.Free() + ensure.Nil(t, err) + ensure.True(t, v6.Data() == nil) + // transaction + txn3 := txdb.TransactionBegin(wo, to, nil) + defer txn3.Destroy() + // delete + ensure.Nil(t, txn3.Delete(givenTxnKey)) + ensure.Nil(t, txn3.Commit()) + + v7, err := db.Get(ro, givenTxnKey) + defer v7.Free() + ensure.Nil(t, err) + ensure.True(t, v7.Data() == nil) + +} + +func newTestOptimisticTransactionDB(t *testing.T, name string, applyOpts func(opts *Options)) *OptimisticTransactionDB { + dir, err := ioutil.TempDir("", "gorockstransactiondb-"+name) + ensure.Nil(t, err) + + opts := NewDefaultOptions() + opts.SetCreateIfMissing(true) + db, err := OpenOptimisticTransactionDb(opts, dir) + ensure.Nil(t, err) + + return db +}