Skip to content

Commit 948a145

Browse files
authored
verbs: implement support for append command (#63)
1 parent 8ad0fbd commit 948a145

2 files changed

Lines changed: 106 additions & 0 deletions

File tree

e2e_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,33 @@ func TestE2E_Replace(t *testing.T) {
201201
})
202202
}
203203

204+
func TestE2E_Append(t *testing.T) {
205+
t.Parallel()
206+
207+
address, done := memctest.LaunchTCP(t, nil)
208+
t.Cleanup(done)
209+
210+
c := New([]string{address})
211+
defer ignore.Close(c)
212+
213+
t.Run("success", func(t *testing.T) {
214+
err := Set(c, "key1", "value1")
215+
must.NoError(t, err)
216+
217+
err = Append(c, "key1", ".appended")
218+
must.NoError(t, err)
219+
220+
v, verr := Get[string](c, "key1")
221+
must.NoError(t, verr)
222+
must.Eq(t, "value1.appended", v)
223+
})
224+
225+
t.Run("not found", func(t *testing.T) {
226+
err := Append(c, "key-does-not-exist", "value")
227+
must.ErrorIs(t, err, ErrNotStored)
228+
})
229+
}
230+
204231
func TestE2E_Increment(t *testing.T) {
205232
t.Parallel()
206233

verbs.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,85 @@ func Replace[T any](c *Client, key string, item T, opts ...Option) error {
203203
})
204204
}
205205

206+
// Append will append the given value to the value associated with the given key.
207+
//
208+
// Append differs from Set in that it is meant to add additional data to an
209+
// existing key, rather than replace the existing value entirely. The key
210+
// must already exist.
211+
//
212+
// Uses Client c to connect to a memcached instance, and automatically handles
213+
// connection pooling and reuse.
214+
//
215+
// One or more Option(s) may be applied to configure things such as the
216+
// value expiration TTL or its associated flags.
217+
func Append[T any](c *Client, key string, item T, opts ...Option) error {
218+
if err := check(key); err != nil {
219+
return err
220+
}
221+
222+
options := &Options{
223+
expiration: c.expiration,
224+
flags: 0,
225+
}
226+
227+
for _, opt := range opts {
228+
opt(options)
229+
}
230+
231+
return c.do(key, func(conn *iopool.Buffer) error {
232+
encoding, encerr := encode(item)
233+
if encerr != nil {
234+
return encerr
235+
}
236+
237+
expiration, experr := c.seconds(options.expiration)
238+
if experr != nil {
239+
return experr
240+
}
241+
242+
// write the header components
243+
if _, err := fmt.Fprintf(
244+
conn,
245+
"append %s %d %d %d\r\n",
246+
key, options.flags, expiration, len(encoding),
247+
); err != nil {
248+
return err
249+
}
250+
251+
// write the payload
252+
if _, err := conn.Write(encoding); err != nil {
253+
return err
254+
}
255+
256+
// write clrf
257+
if _, err := io.WriteString(conn, "\r\n"); err != nil {
258+
return err
259+
}
260+
261+
// flush the buffer
262+
if err := conn.Flush(); err != nil {
263+
return err
264+
}
265+
266+
// read response
267+
line, lerr := conn.ReadSlice('\n')
268+
if lerr != nil {
269+
return lerr
270+
}
271+
272+
switch string(line) {
273+
case "STORED\r\n":
274+
return nil
275+
case "NOT_STORED\r\n":
276+
return ErrNotStored
277+
case "NOT_FOUND\r\n":
278+
return ErrNotFound
279+
default:
280+
return fmt.Errorf("memc: unexpected response to append: %q", string(line))
281+
}
282+
})
283+
}
284+
206285
// Add will store the item using the given key, but only if no item currently
207286
// exists. New items are at the top of the LRU.
208287
//

0 commit comments

Comments
 (0)