commit of code
This commit is contained in:
parent
bf6a38197f
commit
4eae02d0e2
26
cmd/kvrepl/main.go
Normal file
26
cmd/kvrepl/main.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
48
db.go
Normal file
48
db.go
Normal file
@ -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 <key>")
|
||||
}
|
||||
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 <key> <value>")
|
||||
}
|
||||
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 <key>")
|
||||
}
|
||||
_, 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
|
||||
}
|
102
db_test.go
Normal file
102
db_test.go
Normal file
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
59
parse.go
Normal file
59
parse.go
Normal file
@ -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
|
||||
}
|
147
parse_test.go
Normal file
147
parse_test.go
Normal file
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user