Skip to content

Commit 8ad0fbd

Browse files
authored
verbs: implement support for replace command (#62)
1 parent 18ec5de commit 8ad0fbd

2 files changed

Lines changed: 104 additions & 0 deletions

File tree

e2e_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,36 @@ func TestE2E_Add(t *testing.T) {
171171
})
172172
}
173173

174+
func TestE2E_Replace(t *testing.T) {
175+
t.Parallel()
176+
177+
address, done := memctest.LaunchTCP(t, nil)
178+
t.Cleanup(done)
179+
180+
c := New([]string{address})
181+
defer ignore.Close(c)
182+
183+
t.Run("success", func(t *testing.T) {
184+
err := Set(c, "key1", "value1")
185+
must.NoError(t, err)
186+
187+
err = Replace(c, "key1", "value1.replaced")
188+
must.NoError(t, err)
189+
190+
v, verr := Get[string](c, "key1")
191+
must.NoError(t, verr)
192+
must.Eq(t, "value1.replaced", v)
193+
})
194+
195+
t.Run("not found", func(t *testing.T) {
196+
err := Replace(c, "key-does-not-exist", "value")
197+
must.ErrorIs(t, err, ErrNotStored)
198+
199+
_, verr := Get[string](c, "key-does-not-exist")
200+
must.ErrorIs(t, verr, ErrCacheMiss)
201+
})
202+
}
203+
174204
func TestE2E_Increment(t *testing.T) {
175205
t.Parallel()
176206

verbs.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,80 @@ func Set[T any](c *Client, key string, item T, opts ...Option) error {
129129
})
130130
}
131131

132+
// Replace will store the item using the given key, but only if the key
133+
// already exists. New items are at the top of the LRU.
134+
//
135+
// Uses Client c to connect to a memcached instance, and automatically handles
136+
// connection pooling and reuse.
137+
//
138+
// One or more Option(s) may be applied to configure things such as the
139+
// value expiration TTL or its associated flags.
140+
func Replace[T any](c *Client, key string, item T, opts ...Option) error {
141+
if err := check(key); err != nil {
142+
return err
143+
}
144+
145+
options := &Options{
146+
expiration: c.expiration,
147+
flags: 0,
148+
}
149+
150+
for _, opt := range opts {
151+
opt(options)
152+
}
153+
154+
return c.do(key, func(conn *iopool.Buffer) error {
155+
encoding, encerr := encode(item)
156+
if encerr != nil {
157+
return encerr
158+
}
159+
160+
expiration, experr := c.seconds(options.expiration)
161+
if experr != nil {
162+
return experr
163+
}
164+
165+
// write the header components
166+
if _, err := fmt.Fprintf(
167+
conn,
168+
"replace %s %d %d %d\r\n",
169+
key, options.flags, expiration, len(encoding),
170+
); err != nil {
171+
return err
172+
}
173+
174+
// write the payload
175+
if _, err := conn.Write(encoding); err != nil {
176+
return err
177+
}
178+
179+
// write clrf
180+
if _, err := io.WriteString(conn, "\r\n"); err != nil {
181+
return err
182+
}
183+
184+
// flush the buffer
185+
if err := conn.Flush(); err != nil {
186+
return err
187+
}
188+
189+
// read response
190+
line, lerr := conn.ReadSlice('\n')
191+
if lerr != nil {
192+
return lerr
193+
}
194+
195+
switch string(line) {
196+
case "STORED\r\n":
197+
return nil
198+
case "NOT_STORED\r\n":
199+
return ErrNotStored
200+
default:
201+
return fmt.Errorf("memc: unexpected response to replace: %q", string(line))
202+
}
203+
})
204+
}
205+
132206
// Add will store the item using the given key, but only if no item currently
133207
// exists. New items are at the top of the LRU.
134208
//

0 commit comments

Comments
 (0)