xref: /memcached-1.4.29/t/binary.t (revision ee461d11)
1#!/usr/bin/perl
2
3use strict;
4use warnings;
5use Test::More tests => 4942;
6use FindBin qw($Bin);
7use lib "$Bin/lib";
8use MemcachedTest;
9
10my $server = new_memcached();
11ok($server, "started the server");
12
13# Based almost 100% off testClient.py which is:
14# Copyright (c) 2007  Dustin Sallings <[email protected]>
15
16# Command constants
17use constant CMD_GET        => 0x00;
18use constant CMD_SET        => 0x01;
19use constant CMD_ADD        => 0x02;
20use constant CMD_REPLACE    => 0x03;
21use constant CMD_DELETE     => 0x04;
22use constant CMD_INCR       => 0x05;
23use constant CMD_DECR       => 0x06;
24use constant CMD_QUIT       => 0x07;
25use constant CMD_FLUSH      => 0x08;
26use constant CMD_GETQ       => 0x09;
27use constant CMD_NOOP       => 0x0A;
28use constant CMD_VERSION    => 0x0B;
29use constant CMD_GETK       => 0x0C;
30use constant CMD_GETKQ      => 0x0D;
31use constant CMD_APPEND     => 0x0E;
32use constant CMD_PREPEND    => 0x0F;
33use constant CMD_STAT       => 0x10;
34use constant CMD_SETQ       => 0x11;
35use constant CMD_ADDQ       => 0x12;
36use constant CMD_REPLACEQ   => 0x13;
37use constant CMD_DELETEQ    => 0x14;
38use constant CMD_INCREMENTQ => 0x15;
39use constant CMD_DECREMENTQ => 0x16;
40use constant CMD_QUITQ      => 0x17;
41use constant CMD_FLUSHQ     => 0x18;
42use constant CMD_APPENDQ    => 0x19;
43use constant CMD_PREPENDQ   => 0x1A;
44use constant CMD_TOUCH      => 0x1C;
45use constant CMD_GAT        => 0x1D;
46use constant CMD_GATQ       => 0x1E;
47use constant CMD_GATK       => 0x23;
48use constant CMD_GATKQ      => 0x24;
49
50# REQ and RES formats are divided even though they currently share
51# the same format, since they _could_ differ in the future.
52use constant REQ_PKT_FMT      => "CCnCCnNNNN";
53use constant RES_PKT_FMT      => "CCnCCnNNNN";
54use constant INCRDECR_PKT_FMT => "NNNNN";
55use constant MIN_RECV_BYTES   => length(pack(RES_PKT_FMT));
56use constant REQ_MAGIC        => 0x80;
57use constant RES_MAGIC        => 0x81;
58
59my $mc = MC::Client->new;
60
61# Let's turn on detail stats for all this stuff
62
63$mc->stats('detail on');
64my $check = sub {
65    my ($key, $orig_flags, $orig_val) = @_;
66    my ($flags, $val, $cas) = $mc->get($key);
67    is($flags, $orig_flags, "Flags is set properly");
68    ok($val eq $orig_val || $val == $orig_val, $val . " = " . $orig_val);
69};
70
71my $set = sub {
72    my ($key, $exp, $orig_flags, $orig_value) = @_;
73    $mc->set($key, $orig_value, $orig_flags, $exp);
74    $check->($key, $orig_flags, $orig_value);
75};
76
77my $empty = sub {
78    my $key = shift;
79    my $rv =()= eval { $mc->get($key) };
80    is($rv, 0, "Didn't get a result from get");
81    ok($@->not_found, "We got a not found error when we expected one");
82};
83
84my $delete = sub {
85    my ($key, $when) = @_;
86    $mc->delete($key, $when);
87    $empty->($key);
88};
89
90# diag "Test Version";
91my $v = $mc->version;
92ok(defined $v && length($v), "Proper version: $v");
93
94# Bug 71
95{
96    my %stats1 = $mc->stats('');
97    $mc->flush;
98    my %stats2 = $mc->stats('');
99
100    is($stats2{'cmd_flush'}, $stats1{'cmd_flush'} + 1,
101       "Stats not updated on a binary flush");
102}
103
104# diag "Flushing...";
105$mc->flush;
106
107# diag "Noop";
108$mc->noop;
109
110# diag "Simple set/get";
111$set->('x', 5, 19, "somevalue");
112
113# diag "Delete";
114$delete->('x');
115
116# diag "Flush";
117$set->('x', 5, 19, "somevaluex");
118$set->('y', 5, 17, "somevaluey");
119$mc->flush;
120$empty->('x');
121$empty->('y');
122
123{
124    # diag "Some chunked item tests";
125    my $s2 = new_memcached('-o slab_chunk_max=4096');
126    ok($s2, "started the server");
127    my $m2 = MC::Client->new($s2);
128    # Specifically trying to cross the chunk boundary when internally
129    # appending CLRF.
130    for my $k (7900..8100) {
131        my $val = 'd' x $k;
132        $val .= '123';
133        $m2->set('t', $val, 0, 0);
134        # Ensure we get back the same value. Bugs can chop chars.
135        my (undef, $gval, undef) = $m2->get('t');
136        ok($gval eq $val, $gval . " = " . $val);
137    }
138}
139
140{
141    # diag "Add";
142    $empty->('i');
143    $mc->add('i', 'ex', 5, 10);
144    $check->('i', 5, "ex");
145
146    my $rv =()= eval { $mc->add('i', "ex2", 10, 5) };
147    is($rv, 0, "Add didn't return anything");
148    ok($@->exists, "Expected exists error received");
149    $check->('i', 5, "ex");
150}
151
152{
153    # diag "Too big.";
154    $empty->('toobig');
155    $mc->set('toobig', 'not too big', 10, 10);
156    eval {
157        my $bigval = ("x" x (1024*1024)) . "x";
158        $mc->set('toobig', $bigval, 10, 10);
159    };
160    ok($@->too_big, "Was too big");
161    $empty->('toobig');
162}
163
164{
165    # diag "Replace";
166    $empty->('j');
167
168    my $rv =()= eval { $mc->replace('j', "ex", 19, 5) };
169    is($rv, 0, "Replace didn't return anything");
170    ok($@->not_found, "Expected not_found error received");
171    $empty->('j');
172    $mc->add('j', "ex2", 14, 5);
173    $check->('j', 14, "ex2");
174    $mc->replace('j', "ex3", 24, 5);
175    $check->('j', 24, "ex3");
176}
177
178{
179    # diag "MultiGet";
180    $mc->add('xx', "ex", 1, 5);
181    $mc->add('wye', "why", 2, 5);
182    my $rv = $mc->get_multi(qw(xx wye zed));
183
184    # CAS is returned with all gets.
185    $rv->{xx}->[2]  = 0;
186    $rv->{wye}->[2] = 0;
187    is_deeply($rv->{xx}, [1, 'ex', 0], "X is correct");
188    is_deeply($rv->{wye}, [2, 'why', 0], "Y is correct");
189    is(keys(%$rv), 2, "Got only two answers like we expect");
190}
191
192# diag "Test increment";
193$mc->flush;
194is($mc->incr("x"), 0, "First incr call is zero");
195is($mc->incr("x"), 1, "Second incr call is one");
196is($mc->incr("x", 211), 212, "Adding 211 gives you 212");
197is($mc->incr("x", 2**33), 8589934804, "Blast the 32bit border");
198
199# diag "Issue 48 - incrementing plain text.";
200{
201    $mc->set("issue48", "text", 0, 0);
202    my $rv =()= eval { $mc->incr('issue48'); };
203    ok($@ && $@->delta_badval, "Expected invalid value when incrementing text.");
204    $check->('issue48', 0, "text");
205
206    $rv =()= eval { $mc->decr('issue48'); };
207    ok($@ && $@->delta_badval, "Expected invalid value when decrementing text.");
208    $check->('issue48', 0, "text");
209}
210
211# diag "Issue 320 - incr/decr wrong length for initial value";
212{
213    $mc->flush;
214    is($mc->incr("issue320", 1, 1, 0), 1, "incr initial value is 1");
215    my (undef, $rv, undef) = $mc->get("issue320");
216    is(length($rv), 1, "initial value length is 1");
217    is($rv, "1", "initial value is 1");
218}
219
220
221# diag "Test decrement";
222$mc->flush;
223is($mc->incr("x", undef, 5), 5, "Initial value");
224is($mc->decr("x"), 4, "Decrease by one");
225is($mc->decr("x", 211), 0, "Floor is zero");
226
227{
228    # diag "bug220
229    my ($rv, $cas) = $mc->set("bug220", "100", 0, 0);
230    my ($irv, $icas) = $mc->incr_cas("bug220", 999);
231    ok($icas != $cas);
232    is($irv, 1099, "Incr amount failed");
233    my ($flags, $val, $gcas) = $mc->get("bug220");
234    is($gcas, $icas, "CAS didn't match after incr/gets");
235
236    ($irv, $icas) = $mc->incr_cas("bug220", 999);
237    ok($icas != $cas);
238    is($irv, 2098, "Incr amount failed");
239    ($flags, $val, $gcas) = $mc->get("bug220");
240    is($gcas, $icas, "CAS didn't match after incr/gets");
241}
242
243{
244    # diag "bug21";
245    $mc->add("bug21", "9223372036854775807", 0, 0);
246    is($mc->incr("bug21"), 9223372036854775808, "First incr for bug21.");
247    is($mc->incr("bug21"), 9223372036854775809, "Second incr for bug21.");
248    is($mc->decr("bug21"), 9223372036854775808, "Decr for bug21.");
249}
250
251{
252    # diag "CAS";
253    $mc->flush;
254
255    {
256        my $rv =()= eval { $mc->set("x", "bad value", 19, 5, 0x7FFFFFF) };
257        is($rv, 0, "Empty return on expected failure");
258        ok($@->not_found, "Error was 'not found' as expected");
259    }
260
261    my ($r, $rcas) = $mc->add("x", "original value", 5, 19);
262
263    my ($flags, $val, $i) = $mc->get("x");
264    is($val, "original value", "->gets returned proper value");
265    is($rcas, $i, "Add CAS matched.");
266
267    {
268        my $rv =()= eval { $mc->set("x", "broken value", 19, 5, $i+1) };
269        is($rv, 0, "Empty return on expected failure (1)");
270        ok($@->exists, "Expected error state of 'exists' (1)");
271    }
272
273    ($r, $rcas) = $mc->set("x", "new value", 19, 5, $i);
274
275    my ($newflags, $newval, $newi) = $mc->get("x");
276    is($newval, "new value", "CAS properly overwrote value");
277    is($rcas, $newi, "Get CAS matched.");
278
279    {
280        my $rv =()= eval { $mc->set("x", "replay value", 19, 5,  $i) };
281        is($rv, 0, "Empty return on expected failure (2)");
282        ok($@->exists, "Expected error state of 'exists' (2)");
283    }
284}
285
286# diag "Touch commands";
287{
288    $mc->flush;
289    $mc->set("totouch", "toast", 0, 1);
290    my $res = $mc->touch("totouch", 10);
291    sleep 2;
292    $check->("totouch", 0, "toast");
293
294    $mc->set("totouch", "toast2", 0, 1);
295    my ($flags, $val, $i) = $mc->gat("totouch", 10);
296    is($val, "toast2", "GAT returned correct value");
297    sleep 2;
298    $check->("totouch", 0, "toast2");
299
300    # Test miss as well
301    $mc->set("totouch", "toast3", 0, 1);
302    $res = $mc->touch("totouch", 1);
303    sleep 3;
304    $empty->("totouch");
305}
306
307# diag "Silent set.";
308$mc->silent_mutation(::CMD_SETQ, 'silentset', 'silentsetval');
309
310# diag "Silent add.";
311$mc->silent_mutation(::CMD_ADDQ, 'silentadd', 'silentaddval');
312
313# diag "Silent replace.";
314{
315    my $key = "silentreplace";
316    my $extra = pack "NN", 829, 0;
317    $empty->($key);
318    # $mc->send_silent(::CMD_REPLACEQ, $key, 'somevalue', 7278552, $extra, 0);
319    # $empty->($key);
320
321    $mc->add($key, "xval", 831, 0);
322    $check->($key, 831, 'xval');
323
324    $mc->send_silent(::CMD_REPLACEQ, $key, 'somevalue', 7278552, $extra, 0);
325    $check->($key, 829, 'somevalue');
326}
327
328# diag "Silent delete";
329{
330    my $key = "silentdelete";
331    $empty->($key);
332    $mc->set($key, "some val", 19, 0);
333    $mc->send_silent(::CMD_DELETEQ, $key, '', 772);
334    $empty->($key);
335}
336
337# diag "Silent increment";
338{
339    my $key = "silentincr";
340    my $opaque = 98428747;
341    $empty->($key);
342    $mc->silent_incrdecr(::CMD_INCREMENTQ, $key, 0, 0, 0);
343    is($mc->incr($key, 0), 0, "First call is 0");
344
345    $mc->silent_incrdecr(::CMD_INCREMENTQ, $key, 8, 0, 0);
346    is($mc->incr($key, 0), 8);
347}
348
349# diag "Silent decrement";
350{
351    my $key = "silentdecr";
352    my $opaque = 98428147;
353    $empty->($key);
354    $mc->silent_incrdecr(::CMD_DECREMENTQ, $key, 0, 185, 0);
355    is($mc->incr($key, 0), 185);
356
357    $mc->silent_incrdecr(::CMD_DECREMENTQ, $key, 8, 0, 0);
358    is($mc->incr($key, 0), 177);
359}
360
361# diag "Silent flush";
362{
363    my %stats1 = $mc->stats('');
364
365    $set->('x', 5, 19, "somevaluex");
366    $set->('y', 5, 17, "somevaluey");
367    $mc->send_silent(::CMD_FLUSHQ, '', '', 2775256);
368    $empty->('x');
369    $empty->('y');
370
371    my %stats2 = $mc->stats('');
372    is($stats2{'cmd_flush'}, $stats1{'cmd_flush'} + 1,
373       "Stats not updated on a binary quiet flush");
374}
375
376# diag "Append";
377{
378    my $key = "appendkey";
379    my $value = "some value";
380    $set->($key, 8, 19, $value);
381    $mc->_append_prepend(::CMD_APPEND, $key, " more");
382    $check->($key, 19, $value . " more");
383}
384
385# diag "Prepend";
386{
387    my $key = "prependkey";
388    my $value = "some value";
389    $set->($key, 8, 19, $value);
390    $mc->_append_prepend(::CMD_PREPEND, $key, "prefixed ");
391    $check->($key, 19, "prefixed " . $value);
392}
393
394# diag "Silent append";
395{
396    my $key = "appendqkey";
397    my $value = "some value";
398    $set->($key, 8, 19, $value);
399    $mc->send_silent(::CMD_APPENDQ, $key, " more", 7284492);
400    $check->($key, 19, $value . " more");
401}
402
403# diag "Silent prepend";
404{
405    my $key = "prependqkey";
406    my $value = "some value";
407    $set->($key, 8, 19, $value);
408    $mc->send_silent(::CMD_PREPENDQ, $key, "prefixed ", 7284492);
409    $check->($key, 19, "prefixed " . $value);
410}
411
412# diag "Leaky binary get test.";
413# # http://code.google.com/p/memcached/issues/detail?id=16
414{
415    # Get a new socket so we can speak text to it.
416    my $sock = $server->new_sock;
417    my $max = 1024 * 1024;
418    my $big = "a big value that's > .5M and < 1M. ";
419    while (length($big) * 2 < $max) {
420        $big = $big . $big;
421    }
422    my $biglen = length($big);
423
424    for(1..100) {
425        my $key = "some_key_$_";
426        # print STDERR "Key is $key\n";
427        # print $sock "set $key 0 0 $vallen\r\n$value\r\n";
428        print $sock "set $key 0 0 $biglen\r\n$big\r\n";
429        is(scalar <$sock>, "STORED\r\n", "stored big");
430        my ($f, $v, $c) = $mc->get($key);
431    }
432}
433
434# diag "Test stats settings."
435{
436    my %stats = $mc->stats('settings');
437
438    is(1024, $stats{'maxconns'});
439    is('NULL', $stats{'domain_socket'});
440    is('on', $stats{'evictions'});
441    is('yes', $stats{'cas_enabled'});
442    is('yes', $stats{'flush_enabled'});
443}
444
445# diag "Test quit commands.";
446{
447    my $s2 = new_memcached();
448    my $mc2 = MC::Client->new($s2);
449    $mc2->send_command(CMD_QUITQ, '', '', 0, '', 0);
450
451    # Five seconds ought to be enough to get hung up on.
452    my $oldalarmt = alarm(5);
453
454    # Verify we can't read anything.
455    my $bytesread = -1;
456    eval {
457        local $SIG{'ALRM'} = sub { die "timeout" };
458        my $data = "";
459        $bytesread = sysread($mc2->{socket}, $data, 24),
460    };
461    is($bytesread, 0, "Read after quit.");
462
463    # Restore signal stuff.
464    alarm($oldalarmt);
465}
466
467# diag "Test protocol boundary overruns";
468{
469    use List::Util qw[min];
470    # Attempting some protocol overruns by toying around with the edge
471    # of the data buffer at a few different sizes.  This assumes the
472    # boundary is at or around 2048 bytes.
473    for (my $i = 1900; $i < 2100; $i++) {
474        my $k = "test_key_$i";
475        my $v = 'x' x $i;
476        # diag "Trying $i $k";
477        my $extra = pack "NN", 82, 0;
478        my $data = $mc->build_command(::CMD_SETQ, $k, $v, 0, $extra, 0);
479        $data .= $mc->build_command(::CMD_SETQ, "alt_$k", "blah", 0, $extra, 0);
480        if (length($data) > 2024) {
481            for (my $j = 2024; $j < min(2096, length($data)); $j++) {
482                $mc->{socket}->send(substr($data, 0, $j));
483                $mc->flush_socket;
484                sleep(0.001);
485                $mc->{socket}->send(substr($data, $j));
486                $mc->flush_socket;
487            }
488        } else {
489            $mc->{socket}->send($data);
490        }
491        $mc->flush_socket;
492        $check->($k, 82, $v);
493        $check->("alt_$k", 82, "blah");
494    }
495}
496
497# Along with the assertion added to the code to verify we're staying
498# within bounds when we do a stats detail dump (detail turned on at
499# the top).
500my %stats = $mc->stats('detail dump');
501
502# This test causes a disconnection.
503{
504    # diag "Key too large.";
505    my $key = "x" x 365;
506    eval {
507        $mc->get($key, 'should die', 10, 10);
508    };
509    ok($@->einval, "Invalid key length");
510}
511
512# ######################################################################
513# Test ends around here.
514# ######################################################################
515
516package MC::Client;
517
518use strict;
519use warnings;
520use fields qw(socket);
521use IO::Socket::INET;
522
523sub new {
524    my $self = shift;
525    my ($s) = @_;
526    $s = $server unless defined $s;
527    my $sock = $s->sock;
528    $self = fields::new($self);
529    $self->{socket} = $sock;
530    return $self;
531}
532
533sub build_command {
534    my $self = shift;
535    die "Not enough args to send_command" unless @_ >= 4;
536    my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
537
538    $extra_header = '' unless defined $extra_header;
539    my $keylen    = length($key);
540    my $vallen    = length($val);
541    my $extralen  = length($extra_header);
542    my $datatype  = 0;  # field for future use
543    my $reserved  = 0;  # field for future use
544    my $totallen  = $keylen + $vallen + $extralen;
545    my $ident_hi  = 0;
546    my $ident_lo  = 0;
547
548    if ($cas) {
549        $ident_hi = int($cas / 2 ** 32);
550        $ident_lo = int($cas % 2 ** 32);
551    }
552
553    my $msg = pack(::REQ_PKT_FMT, ::REQ_MAGIC, $cmd, $keylen, $extralen,
554                   $datatype, $reserved, $totallen, $opaque, $ident_hi,
555                   $ident_lo);
556    my $full_msg = $msg . $extra_header . $key . $val;
557    return $full_msg;
558}
559
560sub send_command {
561    my $self = shift;
562    die "Not enough args to send_command" unless @_ >= 4;
563    my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
564
565    my $full_msg = $self->build_command($cmd, $key, $val, $opaque, $extra_header, $cas);
566
567    my $sent = $self->{socket}->send($full_msg);
568    die("Send failed:  $!") unless $sent;
569    if($sent != length($full_msg)) {
570        die("only sent $sent of " . length($full_msg) . " bytes");
571    }
572}
573
574sub flush_socket {
575    my $self = shift;
576    $self->{socket}->flush;
577}
578
579# Send a silent command and ensure it doesn't respond.
580sub send_silent {
581    my $self = shift;
582    die "Not enough args to send_silent" unless @_ >= 4;
583    my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
584
585    $self->send_command($cmd, $key, $val, $opaque, $extra_header, $cas);
586    $self->send_command(::CMD_NOOP, '', '', $opaque + 1);
587
588    my ($ropaque, $data) = $self->_handle_single_response;
589    Test::More::is($ropaque, $opaque + 1);
590}
591
592sub silent_mutation {
593    my $self = shift;
594    my ($cmd, $key, $value) = @_;
595
596    $empty->($key);
597    my $extra = pack "NN", 82, 0;
598    $mc->send_silent($cmd, $key, $value, 7278552, $extra, 0);
599    $check->($key, 82, $value);
600}
601
602sub _handle_single_response {
603    my $self = shift;
604    my $myopaque = shift;
605
606    my $hdr = "";
607    while(::MIN_RECV_BYTES - length($hdr) > 0) {
608        $self->{socket}->recv(my $response, ::MIN_RECV_BYTES - length($hdr));
609        $hdr .= $response;
610    }
611    Test::More::is(length($hdr), ::MIN_RECV_BYTES, "Expected read length");
612
613    my ($magic, $cmd, $keylen, $extralen, $datatype, $status, $remaining,
614        $opaque, $ident_hi, $ident_lo) = unpack(::RES_PKT_FMT, $hdr);
615    Test::More::is($magic, ::RES_MAGIC, "Got proper response magic");
616
617    my $cas = ($ident_hi * 2 ** 32) + $ident_lo;
618
619    return ($opaque, '', $cas, 0) if($remaining == 0);
620
621    # fetch the value
622    my $rv="";
623    while($remaining - length($rv) > 0) {
624        $self->{socket}->recv(my $buf, $remaining - length($rv));
625        $rv .= $buf;
626    }
627    if(length($rv) != $remaining) {
628        my $found = length($rv);
629        die("Expected $remaining bytes, got $found");
630    }
631    if (defined $myopaque) {
632        Test::More::is($opaque, $myopaque, "Expected opaque");
633    } else {
634        Test::More::pass("Implicit pass since myopaque is undefined");
635    }
636
637    if ($status) {
638        die MC::Error->new($status, $rv);
639    }
640
641    return ($opaque, $rv, $cas, $keylen);
642}
643
644sub _do_command {
645    my $self = shift;
646    die unless @_ >= 3;
647    my ($cmd, $key, $val, $extra_header, $cas) = @_;
648
649    $extra_header = '' unless defined $extra_header;
650    my $opaque = int(rand(2**32));
651    $self->send_command($cmd, $key, $val, $opaque, $extra_header, $cas);
652    my (undef, $rv, $rcas) = $self->_handle_single_response($opaque);
653    return ($rv, $rcas);
654}
655
656sub _incrdecr_header {
657    my $self = shift;
658    my ($amt, $init, $exp) = @_;
659
660    my $amt_hi = int($amt / 2 ** 32);
661    my $amt_lo = int($amt % 2 ** 32);
662
663    my $init_hi = int($init / 2 ** 32);
664    my $init_lo = int($init % 2 ** 32);
665
666    my $extra_header = pack(::INCRDECR_PKT_FMT, $amt_hi, $amt_lo, $init_hi,
667                            $init_lo, $exp);
668
669    return $extra_header;
670}
671
672sub _incrdecr_cas {
673    my $self = shift;
674    my ($cmd, $key, $amt, $init, $exp) = @_;
675
676    my ($data, $rcas) = $self->_do_command($cmd, $key, '',
677                                           $self->_incrdecr_header($amt, $init, $exp));
678
679    my $header = substr $data, 0, 8, '';
680    my ($resp_hi, $resp_lo) = unpack "NN", $header;
681    my $resp = ($resp_hi * 2 ** 32) + $resp_lo;
682
683    return $resp, $rcas;
684}
685
686sub _incrdecr {
687    my $self = shift;
688    my ($v, $c) = $self->_incrdecr_cas(@_);
689    return $v
690}
691
692sub silent_incrdecr {
693    my $self = shift;
694    my ($cmd, $key, $amt, $init, $exp) = @_;
695    my $opaque = 8275753;
696
697    $mc->send_silent($cmd, $key, '', $opaque,
698                     $mc->_incrdecr_header($amt, $init, $exp));
699}
700
701sub stats {
702    my $self = shift;
703    my $key  = shift;
704    my $cas = 0;
705    my $opaque = int(rand(2**32));
706    $self->send_command(::CMD_STAT, $key, '', $opaque, '', $cas);
707
708    my %rv = ();
709    my $found_key = '';
710    my $found_val = '';
711    do {
712        my ($op, $data, $cas, $keylen) = $self->_handle_single_response($opaque);
713        if($keylen > 0) {
714            $found_key = substr($data, 0, $keylen);
715            $found_val = substr($data, $keylen);
716            $rv{$found_key} = $found_val;
717        } else {
718            $found_key = '';
719        }
720    } while($found_key ne '');
721    return %rv;
722}
723
724sub get {
725    my $self = shift;
726    my $key  = shift;
727    my ($rv, $cas) = $self->_do_command(::CMD_GET, $key, '', '');
728
729    my $header = substr $rv, 0, 4, '';
730    my $flags  = unpack("N", $header);
731
732    return ($flags, $rv, $cas);
733}
734
735sub get_multi {
736    my $self = shift;
737    my @keys = @_;
738
739    for (my $i = 0; $i < @keys; $i++) {
740        $self->send_command(::CMD_GETQ, $keys[$i], '', $i, '', 0);
741    }
742
743    my $terminal = @keys + 10;
744    $self->send_command(::CMD_NOOP, '', '', $terminal);
745
746    my %return;
747    while (1) {
748        my ($opaque, $data) = $self->_handle_single_response;
749        last if $opaque == $terminal;
750
751        my $header = substr $data, 0, 4, '';
752        my $flags  = unpack("N", $header);
753
754        $return{$keys[$opaque]} = [$flags, $data];
755    }
756
757    return %return if wantarray;
758    return \%return;
759}
760
761sub touch {
762    my $self = shift;
763    my ($key, $expire) = @_;
764    my $extra_header = pack "N", $expire;
765    my $cas = 0;
766    return $self->_do_command(::CMD_TOUCH, $key, '', $extra_header, $cas);
767}
768
769sub gat {
770    my $self   = shift;
771    my $key    = shift;
772    my $expire = shift;
773    my $extra_header = pack "N", $expire;
774    my ($rv, $cas) = $self->_do_command(::CMD_GAT, $key, '', $extra_header);
775
776    my $header = substr $rv, 0, 4, '';
777    my $flags  = unpack("N", $header);
778
779    return ($flags, $rv, $cas);
780}
781
782sub version {
783    my $self = shift;
784    return $self->_do_command(::CMD_VERSION, '', '');
785}
786
787sub flush {
788    my $self = shift;
789    return $self->_do_command(::CMD_FLUSH, '', '');
790}
791
792sub add {
793    my $self = shift;
794    my ($key, $val, $flags, $expire) = @_;
795    my $extra_header = pack "NN", $flags, $expire;
796    my $cas = 0;
797    return $self->_do_command(::CMD_ADD, $key, $val, $extra_header, $cas);
798}
799
800sub set {
801    my $self = shift;
802    my ($key, $val, $flags, $expire, $cas) = @_;
803    my $extra_header = pack "NN", $flags, $expire;
804    return $self->_do_command(::CMD_SET, $key, $val, $extra_header, $cas);
805}
806
807sub _append_prepend {
808    my $self = shift;
809    my ($cmd, $key, $val, $cas) = @_;
810    return $self->_do_command($cmd, $key, $val, '', $cas);
811}
812
813sub replace {
814    my $self = shift;
815    my ($key, $val, $flags, $expire) = @_;
816    my $extra_header = pack "NN", $flags, $expire;
817    my $cas = 0;
818    return $self->_do_command(::CMD_REPLACE, $key, $val, $extra_header, $cas);
819}
820
821sub delete {
822    my $self = shift;
823    my ($key) = @_;
824    return $self->_do_command(::CMD_DELETE, $key, '');
825}
826
827sub incr {
828    my $self = shift;
829    my ($key, $amt, $init, $exp) = @_;
830    $amt = 1 unless defined $amt;
831    $init = 0 unless defined $init;
832    $exp = 0 unless defined $exp;
833
834    return $self->_incrdecr(::CMD_INCR, $key, $amt, $init, $exp);
835}
836
837sub incr_cas {
838    my $self = shift;
839    my ($key, $amt, $init, $exp) = @_;
840    $amt = 1 unless defined $amt;
841    $init = 0 unless defined $init;
842    $exp = 0 unless defined $exp;
843
844    return $self->_incrdecr_cas(::CMD_INCR, $key, $amt, $init, $exp);
845}
846
847sub decr {
848    my $self = shift;
849    my ($key, $amt, $init, $exp) = @_;
850    $amt = 1 unless defined $amt;
851    $init = 0 unless defined $init;
852    $exp = 0 unless defined $exp;
853
854    return $self->_incrdecr(::CMD_DECR, $key, $amt, $init, $exp);
855}
856
857sub noop {
858    my $self = shift;
859    return $self->_do_command(::CMD_NOOP, '', '');
860}
861
862package MC::Error;
863
864use strict;
865use warnings;
866
867use constant ERR_UNKNOWN_CMD  => 0x81;
868use constant ERR_NOT_FOUND    => 0x1;
869use constant ERR_EXISTS       => 0x2;
870use constant ERR_TOO_BIG      => 0x3;
871use constant ERR_EINVAL       => 0x4;
872use constant ERR_NOT_STORED   => 0x5;
873use constant ERR_DELTA_BADVAL => 0x6;
874
875use overload '""' => sub {
876    my $self = shift;
877    return "Memcache Error ($self->[0]): $self->[1]";
878};
879
880sub new {
881    my $class = shift;
882    my $error = [@_];
883    my $self = bless $error, (ref $class || $class);
884
885    return $self;
886}
887
888sub not_found {
889    my $self = shift;
890    return $self->[0] == ERR_NOT_FOUND;
891}
892
893sub exists {
894    my $self = shift;
895    return $self->[0] == ERR_EXISTS;
896}
897
898sub too_big {
899    my $self = shift;
900    return $self->[0] == ERR_TOO_BIG;
901}
902
903sub delta_badval {
904    my $self = shift;
905    return $self->[0] == ERR_DELTA_BADVAL;
906}
907
908sub einval {
909    my $self = shift;
910    return $self->[0] == ERR_EINVAL;
911}
912
913# vim: filetype=perl
914
915