Skip to content

Commit ffa29f5

Browse files
authored
verbs: implement support for prepend command (#64)
1 parent 948a145 commit ffa29f5

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
@@ -228,6 +228,33 @@ func TestE2E_Append(t *testing.T) {
228228
})
229229
}
230230

231+
func TestE2E_Prepend(t *testing.T) {
232+
t.Parallel()
233+
234+
address, done := memctest.LaunchTCP(t, nil)
235+
t.Cleanup(done)
236+
237+
c := New([]string{address})
238+
defer ignore.Close(c)
239+
240+
t.Run("success", func(t *testing.T) {
241+
err := Set(c, "key1", "value1")
242+
must.NoError(t, err)
243+
244+
err = Prepend(c, "key1", "prepended.")
245+
must.NoError(t, err)
246+
247+
v, verr := Get[string](c, "key1")
248+
must.NoError(t, verr)
249+
must.Eq(t, "prepended.value1", v)
250+
})
251+
252+
t.Run("not found", func(t *testing.T) {
253+
err := Prepend(c, "key-does-not-exist", "value")
254+
must.ErrorIs(t, err, ErrNotStored)
255+
})
256+
}
257+
231258
func TestE2E_Increment(t *testing.T) {
232259
t.Parallel()
233260

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+
// Prepend will prepend the given value to the value associated with the given key.
207+
//
208+
// Prepend 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 Prepend[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+
"prepend %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 prepend: %q", string(line))
281+
}
282+
})
283+
}
284+
206285
// Append will append the given value to the value associated with the given key.
207286
//
208287
// Append differs from Set in that it is meant to add additional data to an

0 commit comments

Comments
 (0)