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