Quellcode durchsuchen

Unix socket support.

Brad Fitzpatrick vor 14 Jahren
Ursprung
Commit
f6151f4064
2 geänderte Dateien mit 101 neuen und 34 gelöschten Zeilen
  1. 55 16
      memcache.go
  2. 46 18
      memcache_test.go

+ 55 - 16
memcache.go

@@ -23,16 +23,14 @@ import (
 	"fmt"
 	"io"
 	"io/ioutil"
-	"log"
 	"net"
 	"os"
+	"strconv"
 	"strings"
 	"sync"
 	"time"
 )
 
-var _ = log.Printf
-
 // Similar to:
 // http://code.google.com/appengine/docs/go/memcache/reference.html
 
@@ -105,6 +103,8 @@ var (
 	resultNotFound  = []byte("NOT_FOUND\r\n")
 	resultDeleted   = []byte("DELETED\r\n")
 	resultEnd       = []byte("END\r\n")
+
+	resultClientErrorPrefix = []byte("CLIENT_ERROR ")
 )
 
 // New returns a memcache client using the provided server(s)
@@ -145,8 +145,8 @@ type Item struct {
 	// Object is the Item's value for use with a Codec.
 	Object interface{}
 
-	// Flags are server-opaque flags whose semantics are entirely up to the
-	// App Engine app.
+	// Flags are server-opaque flags whose semantics are entirely
+	// up to the app.
 	Flags uint32
 
 	// Expiration is the cache expiration time, in seconds: either a relative
@@ -491,15 +491,20 @@ func (c *Client) populateOne(rw *bufio.ReadWriter, verb string, item *Item) os.E
 	return fmt.Errorf("memcache: unexpected response line from %q: %q", verb, string(line))
 }
 
-func writeExpectf(rw *bufio.ReadWriter, expect []byte, format string, args ...interface{}) os.Error {
+func writeReadLine(rw *bufio.ReadWriter, format string, args ...interface{}) ([]byte, os.Error) {
 	_, err := fmt.Fprintf(rw, format, args...)
 	if err != nil {
-		return err
+		return nil, err
 	}
 	if err := rw.Flush(); err != nil {
-		return err
+		return nil, err
 	}
 	line, err := rw.ReadSlice('\n')
+	return line, err
+}
+
+func writeExpectf(rw *bufio.ReadWriter, expect []byte, format string, args ...interface{}) os.Error {
+	line, err := writeReadLine(rw, format, args...)
 	if err != nil {
 		return err
 	}
@@ -519,15 +524,49 @@ func writeExpectf(rw *bufio.ReadWriter, expect []byte, format string, args ...in
 // Delete deletes the item with the provided key. The error ErrCacheMiss is
 // returned if the item didn't already exist in the cache.
 func (c *Client) Delete(key string) os.Error {
-	return c.DeleteLock(key, 0)
+	return c.withKeyRw(key, func(rw *bufio.ReadWriter) os.Error {
+		return writeExpectf(rw, resultDeleted, "delete %s\r\n", key)
+	})
 }
 
-// Delete deletes the item with the provided key, also instructing the
-// server to not permit an "add" or "replace" commands to work on the
-// key for the given duration (in seconds). The error ErrCacheMiss is
-// returned if the item didn't already exist in the cache.
-func (c *Client) DeleteLock(key string, seconds int) os.Error {
-	return c.withKeyRw(key, func(rw *bufio.ReadWriter) os.Error {
-		return writeExpectf(rw, resultDeleted, "delete %s %d\r\n", key, seconds)
+// Increment atomically increments key by delta. The return value is
+// the new value after being incremented or an error. If the value
+// didn't exist in memcached the error is ErrCacheMiss. The value in
+// memcached must be an decimal number, or an error will be returned.
+// On 64-bit overflow, the new value wraps around.
+func (c *Client) Increment(key string, delta uint64) (newValue uint64, err os.Error) {
+	return c.incrDecr("incr", key, delta)
+}
+
+// Decrement atomically decrements key by delta. The return value is
+// the new value after being decremented or an error. If the value
+// didn't exist in memcached the error is ErrCacheMiss. The value in
+// memcached must be an decimal number, or an error will be returned.
+// On underflow, the new value is capped at zero and does not wrap
+// around.
+func (c *Client) Decrement(key string, delta uint64) (newValue uint64, err os.Error) {
+	return c.incrDecr("decr", key, delta)
+}
+
+func (c *Client) incrDecr(verb, key string, delta uint64) (uint64, os.Error) {
+	var val uint64
+	err := c.withKeyRw(key, func(rw *bufio.ReadWriter) os.Error {
+		line, err := writeReadLine(rw, "%s %s %d\r\n", verb, key, delta)
+		if err != nil {
+			return err
+		}
+		switch {
+		case bytes.Equal(line, resultNotFound):
+			return ErrCacheMiss
+		case bytes.HasPrefix(line, resultClientErrorPrefix):
+			errMsg := line[len(resultClientErrorPrefix) : len(line)-2]
+			return os.NewError("memcache: client error: " + string(errMsg))
+		}
+		val, err = strconv.Atoui64(string(line[:len(line)-2]))
+		if err != nil {
+			return err
+		}
+		return nil
 	})
+	return val, err
 }

+ 46 - 18
memcache_test.go

@@ -22,6 +22,7 @@ import (
 	"fmt"
 	"net"
 	"os"
+	"strings"
 	"testing"
 	"time"
 )
@@ -69,20 +70,28 @@ func TestUnixSocket(t *testing.T) {
 }
 
 func testWithClient(t *testing.T, c *Client) {
-	// Set
-	foo := &Item{Key: "foo", Value: []byte("fooval"), Flags: 123}
-	if err := c.Set(foo); err != nil {
-		t.Fatalf("first set(foo): %v", err)
+	checkErr := func(err os.Error, format string, args ...interface{}) {
+		if err != nil {
+			t.Fatalf(format, args...)
+		}
 	}
-	if err := c.Set(foo); err != nil {
-		t.Fatalf("second set(foo): %v", err)
+
+	mustSet := func(it *Item) {
+		if err := c.Set(it); err != nil {
+			t.Fatalf("failed to Set %#v: %v", *it, err)
+		}
 	}
 
+	// Set
+	foo := &Item{Key: "foo", Value: []byte("fooval"), Flags: 123}
+	err := c.Set(foo)
+	checkErr(err, "first set(foo): %v", err)
+	err = c.Set(foo)
+	checkErr(err, "second set(foo): %v", err)
+
 	// Get
 	it, err := c.Get("foo")
-	if err != nil {
-		t.Fatalf("get(foo): %v", err)
-	}
+	checkErr(err, "get(foo): %v", err)
 	if it.Key != "foo" {
 		t.Errorf("get(foo) Key = %q, want foo", it.Key)
 	}
@@ -95,18 +104,15 @@ func testWithClient(t *testing.T, c *Client) {
 
 	// Add
 	bar := &Item{Key: "bar", Value: []byte("barval")}
-	if err := c.Add(bar); err != nil {
-		t.Fatalf("first add(foo): %v", err)
-	}
+	err = c.Add(bar)
+	checkErr(err, "first add(foo): %v", err)
 	if err := c.Add(bar); err != ErrNotStored {
 		t.Fatalf("second add(foo) want ErrNotStored, got %v", err)
 	}
 
 	// GetMulti
 	m, err := c.GetMulti([]string{"foo", "bar"})
-	if err != nil {
-		t.Fatalf("GetMulti: %v", err)
-	}
+	checkErr(err, "GetMulti: %v", err)
 	if g, e := len(m), 2; g != e {
 		t.Errorf("GetMulti: got len(map) = %d, want = %d", g, e)
 	}
@@ -125,12 +131,34 @@ func testWithClient(t *testing.T, c *Client) {
 
 	// Delete
 	err = c.Delete("foo")
-	if err != nil {
-		t.Errorf("Delete: %v", err)
-	}
+	checkErr(err, "Delete: %v", err)
 	it, err = c.Get("foo")
 	if err != ErrCacheMiss {
 		t.Errorf("post-Delete want ErrCacheMiss, got %v", err)
 	}
 
+	// Incr/Decr
+	mustSet(&Item{Key: "num", Value: []byte("42")})
+	n, err := c.Increment("num", 8)
+	checkErr(err, "Increment num + 8: %v", err)
+	if n != 50 {
+		t.Fatalf("Increment num + 8: want=50, got=%d", n)
+	}
+	n, err = c.Decrement("num", 49)
+	checkErr(err, "Decrement: %v", err)
+	if n != 1 {
+		t.Fatalf("Decrement 49: want=1, got=%d", n)
+	}
+	err = c.Delete("num")
+	checkErr(err, "delete num: %v", err)
+	n, err = c.Increment("num", 1)
+	if err != ErrCacheMiss {
+		t.Fatalf("increment post-delete: want ErrCacheMiss, got %v", err)
+	}
+	mustSet(&Item{Key: "num", Value: []byte("not-numeric")})
+	n, err = c.Increment("num", 1)
+	if err == nil || !strings.Contains(err.String(), "client error") {
+		t.Fatalf("increment non-number: want client error, got %v", err)
+	}
+
 }