diff --git a/clientv3/integration/kv_test.go b/clientv3/integration/kv_test.go index efaa337fd..ee6e3358e 100644 --- a/clientv3/integration/kv_test.go +++ b/clientv3/integration/kv_test.go @@ -113,6 +113,39 @@ func TestKVPut(t *testing.T) { } } +// TestKVPutWithIgnoreValue ensures that Put with WithIgnoreValue does not clobber the old value. +func TestKVPutWithIgnoreValue(t *testing.T) { + defer testutil.AfterTest(t) + + clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1}) + defer clus.Terminate(t) + + kv := clientv3.NewKV(clus.RandClient()) + + _, err := kv.Put(context.TODO(), "foo", "", clientv3.WithIgnoreValue()) + if err != rpctypes.ErrKeyNotFound { + t.Fatalf("err expected %v, got %v", rpctypes.ErrKeyNotFound, err) + } + + if _, err := kv.Put(context.TODO(), "foo", "bar"); err != nil { + t.Fatal(err) + } + + if _, err := kv.Put(context.TODO(), "foo", "", clientv3.WithIgnoreValue()); err != nil { + t.Fatal(err) + } + rr, rerr := kv.Get(context.TODO(), "foo") + if rerr != nil { + t.Fatal(rerr) + } + if len(rr.Kvs) != 1 { + t.Fatalf("len(rr.Kvs) expected 1, got %d", len(rr.Kvs)) + } + if !bytes.Equal(rr.Kvs[0].Value, []byte("bar")) { + t.Fatalf("value expected 'bar', got %q", rr.Kvs[0].Value) + } +} + func TestKVPutWithRequireLeader(t *testing.T) { defer testutil.AfterTest(t) diff --git a/clientv3/kv.go b/clientv3/kv.go index c8350f926..89480a3e4 100644 --- a/clientv3/kv.go +++ b/clientv3/kv.go @@ -148,7 +148,7 @@ func (kv *kv) do(ctx context.Context, op Op) (OpResponse, error) { } case tPut: var resp *pb.PutResponse - r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV} + r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV, IgnoreValue: op.ignoreValue} resp, err = kv.remote.Put(ctx, r) if err == nil { return OpResponse{put: (*PutResponse)(resp)}, nil diff --git a/clientv3/op.go b/clientv3/op.go index 6e2600766..f917af7c8 100644 --- a/clientv3/op.go +++ b/clientv3/op.go @@ -52,6 +52,9 @@ type Op struct { // for watch, put, delete prevKV bool + // for put + ignoreValue bool + // progressNotify is for progress updates. progressNotify bool // createdNotify is for created event @@ -94,7 +97,7 @@ func (op Op) toRequestOp() *pb.RequestOp { case tRange: return &pb.RequestOp{Request: &pb.RequestOp_RequestRange{RequestRange: op.toRangeRequest()}} case tPut: - r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV} + r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV, IgnoreValue: op.ignoreValue} return &pb.RequestOp{Request: &pb.RequestOp_RequestPut{RequestPut: r}} case tDeleteRange: r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end, PrevKv: op.prevKV} @@ -360,6 +363,15 @@ func WithPrevKV() OpOption { } } +// WithIgnoreValue updates the key using its current value. +// Empty value should be passed when ignore_value is set. +// Returns an error if the key does not exist. +func WithIgnoreValue() OpOption { + return func(op *Op) { + op.ignoreValue = true + } +} + // LeaseOp represents an Operation that lease can execute. type LeaseOp struct { id LeaseID