From 4eae02d0e2abfda0a469f8316271ff9bf2470278 Mon Sep 17 00:00:00 2001 From: Derek McQuay Date: Tue, 14 Mar 2017 20:03:29 -0700 Subject: [PATCH] commit of code --- cmd/kvrepl/main.go | 26 ++++++++ db.go | 48 +++++++++++++++ db_test.go | 102 +++++++++++++++++++++++++++++++ parse.go | 59 ++++++++++++++++++ parse_test.go | 147 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 382 insertions(+) create mode 100644 cmd/kvrepl/main.go create mode 100644 db.go create mode 100644 db_test.go create mode 100644 parse.go create mode 100644 parse_test.go diff --git a/cmd/kvrepl/main.go b/cmd/kvrepl/main.go new file mode 100644 index 0000000..7cbbc48 --- /dev/null +++ b/cmd/kvrepl/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "bufio" + "fmt" + "os" + + "s.mcquay.me/dm/kvrepl" +) + +func main() { + d := &kvrepl.DB{PKV: true} + d.KV1 = make(map[string]string) + d.KV2 = make(map[string]string) + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + v, err := kvrepl.Parse(scanner.Text(), d) + if err != nil { + fmt.Fprintln(os.Stderr, err) + } else { + if v != "" { + fmt.Println(v) + } + } + } +} diff --git a/db.go b/db.go new file mode 100644 index 0000000..585bab0 --- /dev/null +++ b/db.go @@ -0,0 +1,48 @@ +package kvrepl + +import "fmt" + +type DB struct { + KV1 map[string]string + KV2 map[string]string + PKV bool + nest int +} + +func read(args []string, kv map[string]string) (string, error) { + if len(args) != 1 { + return "", fmt.Errorf("incorrect usage: READ ") + } + i, ok := kv[args[0]] + if !ok { + return "", fmt.Errorf("key does not exist") + } + return i, nil +} + +func write(args []string, kv map[string]string) error { + if len(args) != 2 { + return fmt.Errorf("incorrect usage: WRITE ") + } + kv[args[0]] = args[1] + return nil +} + +func del(args []string, kv map[string]string) error { + if len(args) != 1 { + return fmt.Errorf("incorrect usage: DELETE ") + } + _, ok := kv[args[0]] + if !ok { + return fmt.Errorf("key does not exist") + } + delete(kv, args[0]) + return nil +} + +func whichDB(d *DB) map[string]string { + if d.PKV { + return d.KV1 + } + return d.KV2 +} diff --git a/db_test.go b/db_test.go new file mode 100644 index 0000000..2e85de4 --- /dev/null +++ b/db_test.go @@ -0,0 +1,102 @@ +package kvrepl + +import "testing" + +func TestRead(t *testing.T) { + var tests = []struct { + args []string + kv map[string]string + expected bool + }{ + {[]string{}, nil, false}, + {[]string{"a", "b"}, nil, false}, + {[]string{"a"}, map[string]string{}, false}, + {[]string{"a"}, map[string]string{"a": "b"}, true}, + } + + for _, rt := range tests { + _, actual := read(rt.args, rt.kv) + if (actual == nil) != rt.expected { + t.Errorf( + "failed read:\n\texpected: %v\n\t actual: %v", + rt.expected, + (actual == nil), + ) + } + } +} + +func TestWrite(t *testing.T) { + var tests = []struct { + args []string + kv map[string]string + expected bool + }{ + {[]string{}, nil, false}, + {[]string{"a"}, nil, false}, + {[]string{"a", "b"}, map[string]string{}, true}, + } + + for _, rt := range tests { + actual := write(rt.args, rt.kv) + if (actual == nil) != rt.expected { + t.Errorf( + "failed write:\n\texpected: %v\n\t actual: %v", + rt.expected, + (actual == nil), + ) + } + } +} + +func TestDel(t *testing.T) { + var tests = []struct { + args []string + kv map[string]string + expected bool + }{ + {[]string{}, nil, false}, + {[]string{"a", "b"}, nil, false}, + {[]string{"a"}, map[string]string{}, false}, + {[]string{"a"}, map[string]string{"a": "b"}, true}, + } + + for _, rt := range tests { + actual := del(rt.args, rt.kv) + if (actual == nil) != rt.expected { + t.Errorf( + "failed del:\n\texpected: %v\n\t actual: %v", + rt.expected, + (actual == nil), + ) + } + } +} + +func TestSeries(t *testing.T) { + kv := make(map[string]string) + err := write([]string{"a", "b"}, kv) + if err != nil { + t.Errorf( + "failed series:\n\tfailed to write", + ) + } + v, err := read([]string{"a"}, kv) + if v != "b" { + t.Errorf( + "failed series:\n\texpected: %v\n\t actual: %v", + "b", + v, + ) + } + err = del([]string{"a"}, kv) + v, err = read([]string{"a"}, kv) + if v != "" { + t.Errorf( + "failed series:\n\texpected: %v\n\t actual: %v", + "", + v, + ) + } + +} diff --git a/parse.go b/parse.go new file mode 100644 index 0000000..66b0733 --- /dev/null +++ b/parse.go @@ -0,0 +1,59 @@ +package kvrepl + +import ( + "fmt" + "os" + "strings" +) + +func Parse(s string, d *DB) (string, error) { + args := strings.Split(s, " ") + + switch strings.ToLower(args[0]) { + case "read": + return read(args[1:], whichDB(d)) + case "write": + return "", write(args[1:], whichDB(d)) + case "delete": + return "", del(args[1:], whichDB(d)) + case "start": + if len(args) != 1 { + return "", fmt.Errorf("incorrect usage: START") + } + d.nest++ + d.PKV = false + + // copy contents of KV1 to KV2 + d.KV2 = make(map[string]string) + for k, v := range d.KV1 { + d.KV2[k] = v + } + case "commit": + if len(args) != 1 { + return "", fmt.Errorf("incorrect usage: COMMIT") + } + if d.PKV { + return "", fmt.Errorf("not in a transaction") + } + if d.nest == 0 { + d.PKV = true + // copy contents of KV2 to KV1 + d.KV1 = make(map[string]string) + for k, v := range d.KV2 { + d.KV1[k] = v + } + } + + case "abort": + if len(args) != 1 { + return "", fmt.Errorf("incorrect usage: ABORT") + } + d.PKV = true + case "quit": + fmt.Println("goodbye") + os.Exit(0) + default: + fmt.Println("not known command") + } + return "", nil +} diff --git a/parse_test.go b/parse_test.go new file mode 100644 index 0000000..cce64ee --- /dev/null +++ b/parse_test.go @@ -0,0 +1,147 @@ +package kvrepl + +import "testing" + +func TestParse(t *testing.T) { + var tests = []struct { + cmds []string + db *DB + expected string + }{ + { + cmds: []string{ + "write a b", + "start", + "write a c", + "abort", + "read a", + }, + db: &DB{ + KV1: map[string]string{}, + KV2: map[string]string{}, + PKV: true, + }, + expected: "b", + }, + { + cmds: []string{ + "write a b", + "start", + "write a hello-again", + "start", + "delete a", + "commit", + "write a once-more", + "abort", + "read a", + }, + db: &DB{ + KV1: map[string]string{}, + KV2: map[string]string{}, + PKV: true, + }, + expected: "b", + }, + { + cmds: []string{ + "write a b", + "start", + "write a hello-again", + "start", + "delete a", + "commit", + "write a once-more", + "start", + "write a foo", + "abort", + "read a", + }, + db: &DB{ + KV1: map[string]string{}, + KV2: map[string]string{}, + PKV: true, + }, + expected: "b", + }, + { + cmds: []string{ + "write a b", + "start", + "write a c", + "commit", + "read a", + }, + db: &DB{ + KV1: map[string]string{}, + KV2: map[string]string{}, + PKV: true, + }, + expected: "c", + }, + { + cmds: []string{ + "write a b", + "start", + "write a c", + "start", + "delete a", + "write a d", + "commit", + "read a", + }, + db: &DB{ + KV1: map[string]string{}, + KV2: map[string]string{}, + PKV: true, + }, + expected: "d", + }, + { + cmds: []string{ + "write a b", + "start", + "write a c", + "start", + "delete a", + "write a d", + "abort", + "read a", + }, + db: &DB{ + KV1: map[string]string{}, + KV2: map[string]string{}, + PKV: true, + }, + expected: "b", + }, + { + cmds: []string{ + "write a b", + "start", + "delete a", + "commit", + "read a", + }, + db: &DB{ + KV1: map[string]string{}, + KV2: map[string]string{}, + PKV: true, + }, + expected: "", + }, + } + + for _, rt := range tests { + actual := "" + for _, c := range rt.cmds { + actual, _ = Parse(c, rt.db) + } + if actual != rt.expected { + t.Errorf( + "failed parse:\n\texpected: %v\n\t actual: %v", + rt.expected, + actual, + ) + } + } +}