2020-02-18 21:35:53 +03:00
|
|
|
package etcd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"github.com/btcsuite/btcwallet/walletdb"
|
|
|
|
)
|
|
|
|
|
|
|
|
// readWriteTx holds a reference to the STM transaction.
|
|
|
|
type readWriteTx struct {
|
|
|
|
// stm is the reference to the parent STM.
|
|
|
|
stm STM
|
|
|
|
|
2020-05-06 20:06:11 +03:00
|
|
|
// active is true if the transaction hasn't been committed yet.
|
2020-02-18 21:35:53 +03:00
|
|
|
active bool
|
2020-05-06 20:06:11 +03:00
|
|
|
|
|
|
|
// dirty is true if we intent to update a value in this transaction.
|
|
|
|
dirty bool
|
|
|
|
|
|
|
|
// lset holds key/value set that we want to lock on. If upon commit the
|
|
|
|
// transaction is dirty and the lset is not empty, we'll bump the mod
|
|
|
|
// version of these key/values.
|
|
|
|
lset map[string]string
|
2020-02-18 21:35:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// newReadWriteTx creates an rw transaction with the passed STM.
|
|
|
|
func newReadWriteTx(stm STM) *readWriteTx {
|
|
|
|
return &readWriteTx{
|
|
|
|
stm: stm,
|
|
|
|
active: true,
|
2020-05-06 20:06:11 +03:00
|
|
|
lset: make(map[string]string),
|
2020-02-18 21:35:53 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// rooBucket is a helper function to return the always present
|
|
|
|
// root bucket.
|
|
|
|
func rootBucket(tx *readWriteTx) *readWriteBucket {
|
2020-05-06 20:06:11 +03:00
|
|
|
return newReadWriteBucket(tx, rootBucketID(), rootBucketID())
|
|
|
|
}
|
|
|
|
|
|
|
|
// lock adds a key value to the lock set.
|
|
|
|
func (tx *readWriteTx) lock(key, val string) {
|
|
|
|
tx.stm.Lock(key)
|
|
|
|
if !tx.dirty {
|
|
|
|
tx.lset[key] = val
|
|
|
|
} else {
|
|
|
|
// Bump the mod version of the key,
|
|
|
|
// leaving the value intact.
|
|
|
|
tx.stm.Put(key, val)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// put updates the passed key/value.
|
|
|
|
func (tx *readWriteTx) put(key, val string) {
|
|
|
|
tx.stm.Put(key, val)
|
|
|
|
tx.setDirty()
|
|
|
|
}
|
|
|
|
|
|
|
|
// del marks the passed key deleted.
|
|
|
|
func (tx *readWriteTx) del(key string) {
|
|
|
|
tx.stm.Del(key)
|
|
|
|
tx.setDirty()
|
|
|
|
}
|
|
|
|
|
|
|
|
// setDirty marks the transaction dirty and bumps
|
|
|
|
// mod version for the existing lock set if it is
|
|
|
|
// not empty.
|
|
|
|
func (tx *readWriteTx) setDirty() {
|
|
|
|
// Bump the lock set.
|
|
|
|
if !tx.dirty && len(tx.lset) > 0 {
|
|
|
|
for key, val := range tx.lset {
|
|
|
|
// Bump the mod version of the key,
|
|
|
|
// leaving the value intact.
|
|
|
|
tx.stm.Put(key, val)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear the lock set.
|
|
|
|
tx.lset = make(map[string]string)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set dirty.
|
|
|
|
tx.dirty = true
|
2020-02-18 21:35:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// ReadBucket opens the root bucket for read only access. If the bucket
|
|
|
|
// described by the key does not exist, nil is returned.
|
|
|
|
func (tx *readWriteTx) ReadBucket(key []byte) walletdb.ReadBucket {
|
|
|
|
return rootBucket(tx).NestedReadWriteBucket(key)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rollback closes the transaction, discarding changes (if any) if the
|
|
|
|
// database was modified by a write transaction.
|
|
|
|
func (tx *readWriteTx) Rollback() error {
|
|
|
|
// If the transaction has been closed roolback will fail.
|
|
|
|
if !tx.active {
|
|
|
|
return walletdb.ErrTxClosed
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rollback the STM and set the tx to inactive.
|
|
|
|
tx.stm.Rollback()
|
|
|
|
tx.active = false
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadWriteBucket opens the root bucket for read/write access. If the
|
|
|
|
// bucket described by the key does not exist, nil is returned.
|
|
|
|
func (tx *readWriteTx) ReadWriteBucket(key []byte) walletdb.ReadWriteBucket {
|
|
|
|
return rootBucket(tx).NestedReadWriteBucket(key)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateTopLevelBucket creates the top level bucket for a key if it
|
|
|
|
// does not exist. The newly-created bucket it returned.
|
|
|
|
func (tx *readWriteTx) CreateTopLevelBucket(key []byte) (walletdb.ReadWriteBucket, error) {
|
|
|
|
return rootBucket(tx).CreateBucketIfNotExists(key)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteTopLevelBucket deletes the top level bucket for a key. This
|
|
|
|
// errors if the bucket can not be found or the key keys a single value
|
|
|
|
// instead of a bucket.
|
|
|
|
func (tx *readWriteTx) DeleteTopLevelBucket(key []byte) error {
|
|
|
|
return rootBucket(tx).DeleteNestedBucket(key)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Commit commits the transaction if not already committed. Will return
|
|
|
|
// error if the underlying STM fails.
|
|
|
|
func (tx *readWriteTx) Commit() error {
|
|
|
|
// Commit will fail if the transaction is already committed.
|
|
|
|
if !tx.active {
|
|
|
|
return walletdb.ErrTxClosed
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try committing the transaction.
|
|
|
|
if err := tx.stm.Commit(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark the transaction as not active after commit.
|
|
|
|
tx.active = false
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// OnCommit sets the commit callback (overriding if already set).
|
|
|
|
func (tx *readWriteTx) OnCommit(cb func()) {
|
|
|
|
tx.stm.OnCommit(cb)
|
|
|
|
}
|