-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
256 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package database | ||
|
||
import "fmt" | ||
|
||
// Database interface | ||
type Database interface { | ||
// Set the key to given value | ||
Set(key string, value int) | ||
|
||
// Get the value for the given key, set 'ok' to true if key exists | ||
Get(key string) (value int, ok bool) | ||
|
||
// Unset the key, making it just like that key was never set | ||
Unset(key string) | ||
|
||
// Begin opens a new transaction | ||
Begin() | ||
|
||
// Commit closes all open transaction blocks, permanently apply the | ||
// changes made in them. | ||
Commit() error | ||
|
||
// Rollback undoes all of the commands issued in the most recent | ||
// transaction block, and closes the block. | ||
Rollback() error | ||
} | ||
|
||
type db struct { | ||
data map[string]int | ||
logs []map[string]int | ||
} | ||
|
||
// NewDatabase returns the Database interface | ||
func NewDatabase() Database { | ||
return &db{data: make(map[string]int)} | ||
} | ||
|
||
// Set the key to given value | ||
func (d *db) Set(key string, value int) { | ||
d.data[key] = value | ||
} | ||
|
||
// Get the value for the given key, set 'ok' to true if key exists | ||
func (d *db) Get(key string) (int, bool) { | ||
value, ok := d.data[key] | ||
return value, ok | ||
} | ||
|
||
// Unset the key, making it just like that key was never set | ||
func (d *db) Unset(key string) { | ||
delete(d.data, key) | ||
} | ||
|
||
// Begin opens a new transaction | ||
func (d *db) Begin() { | ||
// Save a snapshot of the current state of the data to the logs. | ||
snapshot := make(map[string]int) | ||
for k, v := range d.data { | ||
snapshot[k] = v | ||
} | ||
d.logs = append(d.logs, snapshot) | ||
} | ||
|
||
// Commit closes all open transaction blocks, permanently apply the | ||
// changes made in them. | ||
func (d *db) Commit() error { | ||
// Check if there is anything to commit. | ||
if len(d.logs) == 0 { | ||
return fmt.Errorf("nothing to commit") | ||
} | ||
|
||
d.logs = nil | ||
return nil | ||
} | ||
|
||
// Rollback undoes all of the commands issued in the most recent | ||
// transaction block, and closes the block. | ||
func (d *db) Rollback() error { | ||
// Check if there is anything to rollback. | ||
if len(d.logs) == 0 { | ||
return fmt.Errorf("nothing to rollback") | ||
} | ||
|
||
// Restore the data to the last snapshot in the logs. | ||
d.data = d.logs[len(d.logs)-1] | ||
d.logs = d.logs[:len(d.logs)-1] | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
package database | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestUnset(t *testing.T) { | ||
db := NewDatabase() | ||
|
||
db.Set("ex", 10) | ||
assertValue(t, db, "ex", 10) | ||
|
||
db.Unset("ex") | ||
assertUnset(t, db, "ex") | ||
} | ||
|
||
func TestRollback(t *testing.T) { | ||
db := NewDatabase() | ||
|
||
db.Begin() | ||
db.Set("a", 10) | ||
assertValue(t, db, "a", 10) | ||
|
||
db.Begin() | ||
db.Set("a", 20) | ||
assertValue(t, db, "a", 20) | ||
|
||
err := db.Rollback() | ||
assertNoError(t, err) | ||
assertValue(t, db, "a", 10) | ||
|
||
err = db.Rollback() | ||
assertNoError(t, err) | ||
assertUnset(t, db, "a") | ||
} | ||
|
||
func TestNestedCommit(t *testing.T) { | ||
db := NewDatabase() | ||
|
||
db.Begin() | ||
db.Set("a", 30) | ||
|
||
db.Begin() | ||
db.Set("a", 40) | ||
|
||
err := db.Commit() | ||
assertNoError(t, err) | ||
|
||
assertValue(t, db, "a", 40) | ||
|
||
err = db.Rollback() | ||
assertError(t, err) | ||
|
||
err = db.Commit() | ||
assertError(t, err) | ||
} | ||
|
||
func TestTransactionInterleavedKeys(t *testing.T) { | ||
db := NewDatabase() | ||
|
||
db.Set("a", 10) | ||
db.Set("b", 10) | ||
assertValue(t, db, "a", 10) | ||
assertValue(t, db, "b", 10) | ||
|
||
db.Begin() | ||
db.Set("a", 20) | ||
assertValue(t, db, "a", 20) | ||
assertValue(t, db, "b", 10) | ||
|
||
db.Begin() | ||
db.Set("b", 30) | ||
assertValue(t, db, "a", 20) | ||
assertValue(t, db, "b", 30) | ||
|
||
db.Rollback() | ||
assertValue(t, db, "a", 20) | ||
assertValue(t, db, "b", 10) | ||
|
||
db.Rollback() | ||
assertValue(t, db, "a", 10) | ||
assertValue(t, db, "b", 10) | ||
} | ||
|
||
func TestTransactionRollbackUnset(t *testing.T) { | ||
db := NewDatabase() | ||
|
||
db.Set("a", 10) | ||
assertValue(t, db, "a", 10) | ||
|
||
db.Begin() | ||
assertValue(t, db, "a", 10) | ||
db.Set("a", 20) | ||
assertValue(t, db, "a", 20) | ||
|
||
db.Begin() | ||
db.Unset("a") | ||
assertUnset(t, db, "a") | ||
|
||
err := db.Rollback() | ||
assertNoError(t, err) | ||
assertValue(t, db, "a", 20) | ||
|
||
err = db.Commit() | ||
assertNoError(t, err) | ||
assertValue(t, db, "a", 20) | ||
} | ||
|
||
func TestTransactionCommitUnset(t *testing.T) { | ||
db := NewDatabase() | ||
|
||
db.Set("a", 10) | ||
assertValue(t, db, "a", 10) | ||
|
||
db.Begin() | ||
assertValue(t, db, "a", 10) | ||
db.Unset("a") | ||
assertUnset(t, db, "a") | ||
|
||
err := db.Rollback() | ||
assertNoError(t, err) | ||
assertValue(t, db, "a", 10) | ||
|
||
db.Begin() | ||
db.Unset("a") | ||
assertUnset(t, db, "a") | ||
|
||
db.Commit() | ||
assertUnset(t, db, "a") | ||
|
||
db.Begin() | ||
assertUnset(t, db, "a") | ||
db.Set("a", 20) | ||
assertValue(t, db, "a", 20) | ||
|
||
db.Commit() | ||
assertValue(t, db, "a", 20) | ||
} | ||
|
||
func assertUnset(t *testing.T, db Database, key string) { | ||
t.Helper() | ||
_, ok := db.Get(key) | ||
if ok { | ||
t.Fatalf("key %q should not exist", key) | ||
} | ||
} | ||
|
||
func assertValue(t *testing.T, db Database, key string, value int) { | ||
t.Helper() | ||
v, ok := db.Get(key) | ||
if !ok || value != v { | ||
t.Fatalf("db.Get(%q) should return %d, true: got %d, %v", key, value, v, ok) | ||
} | ||
} | ||
|
||
func assertNoError(t *testing.T, err error) { | ||
t.Helper() | ||
if err != nil { | ||
t.Fatalf("expected no error, got: %v", err) | ||
} | ||
} | ||
|
||
func assertError(t *testing.T, err error) { | ||
t.Helper() | ||
if err == nil { | ||
t.Fatal("expected an error, got: nil") | ||
} | ||
} |