xref: /memcached-1.4.29/t/binary.t (revision fba0a891)
1#!/usr/bin/perl
2
3use strict;
4use warnings;
5use Test::More tests => 2880;
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;
44
45# REQ and RES formats are divided even though they currently share
46# the same format, since they _could_ differ in the future.
47use constant REQ_PKT_FMT      => "CCnCCnNNNN";
48use constant RES_PKT_FMT      => "CCnCCnNNNN";
49use constant INCRDECR_PKT_FMT => "NNNNN";
50use constant MIN_RECV_BYTES   => length(pack(RES_PKT_FMT));
51use constant REQ_MAGIC        => 0x80;
52use constant RES_MAGIC        => 0x81;
53
54my $mc = MC::Client->new;
55
56# Let's turn on detail stats for all this stuff
57
58$mc->stats('detail on');
59
60my $check = sub {
61    my ($key, $orig_flags, $orig_val) = @_;
62    my ($flags, $val, $cas) = $mc->get($key);
63    is($flags, $orig_flags, "Flags is set properly");
64    ok($val eq $orig_val || $val == $orig_val, $val . " = " . $orig_val);
65};
66
67my $set = sub {
68    my ($key, $exp, $orig_flags, $orig_value) = @_;
69    $mc->set($key, $orig_value, $orig_flags, $exp);
70    $check->($key, $orig_flags, $orig_value);
71};
72
73my $empty = sub {
74    my $key = shift;
75    my $rv =()= eval { $mc->get($key) };
76    is($rv, 0, "Didn't get a result from get");
77    ok($@->not_found, "We got a not found error when we expected one");
78};
79
80my $delete = sub {
81    my ($key, $when) = @_;
82    $mc->delete($key, $when);
83    $empty->($key);
84};
85
86# diag "Test Version";
87my $v = $mc->version;
88ok(defined $v && length($v), "Proper version: $v");
89
90# diag "Flushing...";
91$mc->flush;
92
93# diag "Noop";
94$mc->noop;
95
96# diag "Simple set/get";
97$set->('x', 5, 19, "somevalue");
98
99# diag "Delete";
100$delete->('x');
101
102# diag "Flush";
103$set->('x', 5, 19, "somevaluex");
104$set->('y', 5, 17, "somevaluey");
105$mc->flush;
106$empty->('x');
107$empty->('y');
108
109{
110    # diag "Add";
111    $empty->('i');
112    $mc->add('i', 'ex', 5, 10);
113    $check->('i', 5, "ex");
114
115    my $rv =()= eval { $mc->add('i', "ex2", 10, 5) };
116    is($rv, 0, "Add didn't return anything");
117    ok($@->exists, "Expected exists error received");
118    $check->('i', 5, "ex");
119}
120
121{
122    # diag "Too big.";
123    $empty->('toobig');
124    $mc->set('toobig', 'not too big', 10, 10);
125    eval {
126        my $bigval = ("x" x (1024*1024)) . "x";
127        $mc->set('toobig', $bigval, 10, 10);
128    };
129    ok($@->too_big, "Was too big");
130    $empty->('toobig');
131}
132
133{
134    # diag "Replace";
135    $empty->('j');
136
137    my $rv =()= eval { $mc->replace('j', "ex", 19, 5) };
138    is($rv, 0, "Replace didn't return anything");
139    ok($@->not_found, "Expected not_found error received");
140    $empty->('j');
141    $mc->add('j', "ex2", 14, 5);
142    $check->('j', 14, "ex2");
143    $mc->replace('j', "ex3", 24, 5);
144    $check->('j', 24, "ex3");
145}
146
147{
148    # diag "MultiGet";
149    $mc->add('xx', "ex", 1, 5);
150    $mc->add('wye', "why", 2, 5);
151    my $rv = $mc->get_multi(qw(xx wye zed));
152
153    # CAS is returned with all gets.
154    $rv->{xx}->[2]  = 0;
155    $rv->{wye}->[2] = 0;
156    is_deeply($rv->{xx}, [1, 'ex', 0], "X is correct");
157    is_deeply($rv->{wye}, [2, 'why', 0], "Y is correct");
158    is(keys(%$rv), 2, "Got only two answers like we expect");
159}
160
161# diag "Test increment";
162$mc->flush;
163is($mc->incr("x"), 0, "First incr call is zero");
164is($mc->incr("x"), 1, "Second incr call is one");
165is($mc->incr("x", 211), 212, "Adding 211 gives you 212");
166is($mc->incr("x", 2**33), 8589934804, "Blast the 32bit border");
167
168# diag "Issue 48 - incrementing plain text.";
169{
170    $mc->set("issue48", "text", 0, 0);
171    my $rv =()= eval { $mc->incr('issue48'); };
172    ok($@ && $@->delta_badval, "Expected invalid value when incrementing text.");
173    $check->('issue48', 0, "text");
174
175    $rv =()= eval { $mc->decr('issue48'); };
176    ok($@ && $@->delta_badval, "Expected invalid value when decrementing text.");
177    $check->('issue48', 0, "text");
178}
179
180
181# diag "Test decrement";
182$mc->flush;
183is($mc->incr("x", undef, 5), 5, "Initial value");
184is($mc->decr("x"), 4, "Decrease by one");
185is($mc->decr("x", 211), 0, "Floor is zero");
186
187{
188    # diag "bug21";
189    $mc->add("bug21", "9223372036854775807", 0, 0);
190    is($mc->incr("bug21"), 9223372036854775808, "First incr for bug21.");
191    is($mc->incr("bug21"), 9223372036854775809, "Second incr for bug21.");
192    is($mc->decr("bug21"), 9223372036854775808, "Decr for bug21.");
193}
194
195{
196    # diag "CAS";
197    $mc->flush;
198
199    {
200        my $rv =()= eval { $mc->set("x", "bad value", 19, 5, 0x7FFFFFF) };
201        is($rv, 0, "Empty return on expected failure");
202        ok($@->not_found, "Error was 'not found' as expected");
203    }
204
205    $mc->add("x", "original value", 5, 19);
206
207    my ($flags, $val, $i) = $mc->get("x");
208    is($val, "original value", "->gets returned proper value");
209
210    {
211        my $rv =()= eval { $mc->set("x", "broken value", 19, 5, $i+1) };
212        is($rv, 0, "Empty return on expected failure (1)");
213        ok($@->exists, "Expected error state of 'exists' (1)");
214    }
215
216    $mc->set("x", "new value", 19, 5, $i);
217
218    my ($newflags, $newval, $newi) = $mc->get("x");
219    is($newval, "new value", "CAS properly overwrote value");
220
221    {
222        my $rv =()= eval { $mc->set("x", "replay value", 19, 5,  $i) };
223        is($rv, 0, "Empty return on expected failure (2)");
224        ok($@->exists, "Expected error state of 'exists' (2)");
225    }
226}
227
228# diag "Silent set.";
229$mc->silent_mutation(::CMD_SETQ, 'silentset', 'silentsetval');
230
231# diag "Silent add.";
232$mc->silent_mutation(::CMD_ADDQ, 'silentadd', 'silentaddval');
233
234# diag "Silent replace.";
235{
236    my $key = "silentreplace";
237    my $extra = pack "NN", 829, 0;
238    $empty->($key);
239    # $mc->send_silent(::CMD_REPLACEQ, $key, 'somevalue', 7278552, $extra, 0);
240    # $empty->($key);
241
242    $mc->add($key, "xval", 831, 0);
243    $check->($key, 831, 'xval');
244
245    $mc->send_silent(::CMD_REPLACEQ, $key, 'somevalue', 7278552, $extra, 0);
246    $check->($key, 829, 'somevalue');
247}
248
249# diag "Silent delete";
250{
251    my $key = "silentdelete";
252    $empty->($key);
253    $mc->set($key, "some val", 19, 0);
254    $mc->send_silent(::CMD_DELETEQ, $key, '', 772);
255    $empty->($key);
256}
257
258# diag "Silent increment";
259{
260    my $key = "silentincr";
261    my $opaque = 98428747;
262    $empty->($key);
263    $mc->silent_incrdecr(::CMD_INCREMENTQ, $key, 0, 0, 0);
264    is($mc->incr($key, 0), 0, "First call is 0");
265
266    $mc->silent_incrdecr(::CMD_INCREMENTQ, $key, 8, 0, 0);
267    is($mc->incr($key, 0), 8);
268}
269
270# diag "Silent decrement";
271{
272    my $key = "silentdecr";
273    my $opaque = 98428147;
274    $empty->($key);
275    $mc->silent_incrdecr(::CMD_DECREMENTQ, $key, 0, 185, 0);
276    is($mc->incr($key, 0), 185);
277
278    $mc->silent_incrdecr(::CMD_DECREMENTQ, $key, 8, 0, 0);
279    is($mc->incr($key, 0), 177);
280}
281
282# diag "Silent flush";
283{
284    $set->('x', 5, 19, "somevaluex");
285    $set->('y', 5, 17, "somevaluey");
286    $mc->send_silent(::CMD_FLUSHQ, '', '', 2775256);
287    $empty->('x');
288    $empty->('y');
289}
290
291# diag "Append";
292{
293    my $key = "appendkey";
294    my $value = "some value";
295    $set->($key, 8, 19, $value);
296    $mc->_append_prepend(::CMD_APPEND, $key, " more");
297    $check->($key, 19, $value . " more");
298}
299
300# diag "Prepend";
301{
302    my $key = "prependkey";
303    my $value = "some value";
304    $set->($key, 8, 19, $value);
305    $mc->_append_prepend(::CMD_PREPEND, $key, "prefixed ");
306    $check->($key, 19, "prefixed " . $value);
307}
308
309# diag "Silent append";
310{
311    my $key = "appendqkey";
312    my $value = "some value";
313    $set->($key, 8, 19, $value);
314    $mc->send_silent(::CMD_APPENDQ, $key, " more", 7284492);
315    $check->($key, 19, $value . " more");
316}
317
318# diag "Silent prepend";
319{
320    my $key = "prependqkey";
321    my $value = "some value";
322    $set->($key, 8, 19, $value);
323    $mc->send_silent(::CMD_PREPENDQ, $key, "prefixed ", 7284492);
324    $check->($key, 19, "prefixed " . $value);
325}
326
327# diag "Leaky binary get test.";
328# # http://code.google.com/p/memcached/issues/detail?id=16
329{
330    # Get a new socket so we can speak text to it.
331    my $sock = $server->new_sock;
332    my $max = 1024 * 1024;
333    my $big = "a big value that's > .5M and < 1M. ";
334    while (length($big) * 2 < $max) {
335        $big = $big . $big;
336    }
337    my $biglen = length($big);
338
339    for(1..100) {
340        my $key = "some_key_$_";
341        # print STDERR "Key is $key\n";
342        # print $sock "set $key 0 0 $vallen\r\n$value\r\n";
343        print $sock "set $key 0 0 $biglen\r\n$big\r\n";
344        is(scalar <$sock>, "STORED\r\n", "stored big");
345        my ($f, $v, $c) = $mc->get($key);
346    }
347}
348
349# diag "Test stats settings."
350{
351    my %stats = $mc->stats('settings');
352
353    is(1024, $stats{'maxconns'});
354    is('NULL', $stats{'domain_socket'});
355    is('on', $stats{'evictions'});
356    is('yes', $stats{'cas_enabled'});
357}
358
359# diag "Test protocol boundary overruns";
360{
361    use List::Util qw[min];
362    # Attempting some protocol overruns by toying around with the edge
363    # of the data buffer at a few different sizes.  This assumes the
364    # boundary is at or around 2048 bytes.
365    for (my $i = 1900; $i < 2100; $i++) {
366        my $k = "test_key_$i";
367        my $v = 'x' x $i;
368        # diag "Trying $i $k";
369        my $extra = pack "NN", 82, 0;
370        my $data = $mc->build_command(::CMD_SETQ, $k, $v, 0, $extra, 0);
371        $data .= $mc->build_command(::CMD_SETQ, "alt_$k", "blah", 0, $extra, 0);
372        if (length($data) > 2024) {
373            for (my $j = 2024; $j < min(2096, length($data)); $j++) {
374                $mc->{socket}->send(substr($data, 0, $j));
375                $mc->flush_socket;
376                sleep(0.001);
377                $mc->{socket}->send(substr($data, $j));
378                $mc->flush_socket;
379            }
380        } else {
381            $mc->{socket}->send($data);
382        }
383        $mc->flush_socket;
384        $check->($k, 82, $v);
385        $check->("alt_$k", 82, "blah");
386    }
387}
388
389# Along with the assertion added to the code to verify we're staying
390# within bounds when we do a stats detail dump (detail turned on at
391# the top).
392my %stats = $mc->stats('detail dump');
393
394# ######################################################################
395# Test ends around here.
396# ######################################################################
397
398package MC::Client;
399
400use strict;
401use warnings;
402use fields qw(socket);
403use IO::Socket::INET;
404
405sub new {
406    my $self = shift;
407    my $sock = $server->sock;
408    $self = fields::new($self);
409    $self->{socket} = $sock;
410    return $self;
411}
412
413sub build_command {
414    my $self = shift;
415    die "Not enough args to send_command" unless @_ >= 4;
416    my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
417
418    $extra_header = '' unless defined $extra_header;
419    my $keylen    = length($key);
420    my $vallen    = length($val);
421    my $extralen  = length($extra_header);
422    my $datatype  = 0;  # field for future use
423    my $reserved  = 0;  # field for future use
424    my $totallen  = $keylen + $vallen + $extralen;
425    my $ident_hi  = 0;
426    my $ident_lo  = 0;
427
428    if ($cas) {
429        $ident_hi = int($cas / 2 ** 32);
430        $ident_lo = int($cas % 2 ** 32);
431    }
432
433    my $msg = pack(::REQ_PKT_FMT, ::REQ_MAGIC, $cmd, $keylen, $extralen,
434                   $datatype, $reserved, $totallen, $opaque, $ident_hi,
435                   $ident_lo);
436    my $full_msg = $msg . $extra_header . $key . $val;
437    return $full_msg;
438}
439
440sub send_command {
441    my $self = shift;
442    die "Not enough args to send_command" unless @_ >= 4;
443    my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
444
445    my $full_msg = $self->build_command($cmd, $key, $val, $opaque, $extra_header, $cas);
446
447    my $sent = $self->{socket}->send($full_msg);
448    if($sent != length($full_msg)) {
449        die("only sent $sent of " . length($full_msg) . " bytes");
450    }
451}
452
453sub flush_socket {
454    my $self = shift;
455    $self->{socket}->flush;
456}
457
458# Send a silent command and ensure it doesn't respond.
459sub send_silent {
460    my $self = shift;
461    die "Not enough args to send_silent" unless @_ >= 4;
462    my ($cmd, $key, $val, $opaque, $extra_header, $cas) = @_;
463
464    $self->send_command($cmd, $key, $val, $opaque, $extra_header, $cas);
465    $self->send_command(::CMD_NOOP, '', '', $opaque + 1);
466
467    my ($ropaque, $data) = $self->_handle_single_response;
468    Test::More::is($ropaque, $opaque + 1);
469}
470
471sub silent_mutation {
472    my $self = shift;
473    my ($cmd, $key, $value) = @_;
474
475    $empty->($key);
476    my $extra = pack "NN", 82, 0;
477    $mc->send_silent($cmd, $key, $value, 7278552, $extra, 0);
478    $check->($key, 82, $value);
479}
480
481sub _handle_single_response {
482    my $self = shift;
483    my $myopaque = shift;
484
485    $self->{socket}->recv(my $response, ::MIN_RECV_BYTES);
486    Test::More::is(length($response), ::MIN_RECV_BYTES, "Expected read length");
487
488    my ($magic, $cmd, $keylen, $extralen, $datatype, $status, $remaining,
489        $opaque, $ident_hi, $ident_lo) = unpack(::RES_PKT_FMT, $response);
490    Test::More::is($magic, ::RES_MAGIC, "Got proper response magic");
491
492    return ($opaque, '', '', 0) if($remaining == 0);
493
494    # fetch the value
495    my $rv="";
496    while($remaining - length($rv) > 0) {
497        $self->{socket}->recv(my $buf, $remaining - length($rv));
498        $rv .= $buf;
499    }
500    if(length($rv) != $remaining) {
501        my $found = length($rv);
502        die("Expected $remaining bytes, got $found");
503    }
504
505    if (defined $myopaque) {
506        Test::More::is($opaque, $myopaque, "Expected opaque");
507    } else {
508        Test::More::pass("Implicit pass since myopaque is undefined");
509    }
510
511    my $cas = ($ident_hi * 2 ** 32) + $ident_lo;
512
513    if ($status) {
514        die MC::Error->new($status, $rv);
515    }
516
517    return ($opaque, $rv, $cas, $keylen);
518}
519
520sub _do_command {
521    my $self = shift;
522    die unless @_ >= 3;
523    my ($cmd, $key, $val, $extra_header, $cas) = @_;
524
525    $extra_header = '' unless defined $extra_header;
526    my $opaque = int(rand(2**32));
527    $self->send_command($cmd, $key, $val, $opaque, $extra_header, $cas);
528    my (undef, $rv, $rcas) = $self->_handle_single_response($opaque);
529    return ($rv, $rcas);
530}
531
532sub _incrdecr_header {
533    my $self = shift;
534    my ($amt, $init, $exp) = @_;
535
536    my $amt_hi = int($amt / 2 ** 32);
537    my $amt_lo = int($amt % 2 ** 32);
538
539    my $init_hi = int($init / 2 ** 32);
540    my $init_lo = int($init % 2 ** 32);
541
542    my $extra_header = pack(::INCRDECR_PKT_FMT, $amt_hi, $amt_lo, $init_hi,
543                            $init_lo, $exp);
544
545    return $extra_header;
546}
547
548sub _incrdecr {
549    my $self = shift;
550    my ($cmd, $key, $amt, $init, $exp) = @_;
551
552    my ($data, undef) = $self->_do_command($cmd, $key, '',
553                                           $self->_incrdecr_header($amt, $init, $exp));
554
555    my $header = substr $data, 0, 8, '';
556    my ($resp_hi, $resp_lo) = unpack "NN", $header;
557    my $resp = ($resp_hi * 2 ** 32) + $resp_lo;
558
559    return $resp;
560}
561
562sub silent_incrdecr {
563    my $self = shift;
564    my ($cmd, $key, $amt, $init, $exp) = @_;
565    my $opaque = 8275753;
566
567    $mc->send_silent($cmd, $key, '', $opaque,
568                     $mc->_incrdecr_header($amt, $init, $exp));
569}
570
571sub stats {
572    my $self = shift;
573    my $key  = shift;
574    my $cas = 0;
575    my $opaque = int(rand(2**32));
576    $self->send_command(::CMD_STAT, $key, '', $opaque, '', $cas);
577
578    my %rv = ();
579    my $found_key = '';
580    my $found_val = '';
581    do {
582        my ($op, $data, $cas, $keylen) = $self->_handle_single_response($opaque);
583        if($keylen > 0) {
584            $found_key = substr($data, 0, $keylen);
585            $found_val = substr($data, $keylen);
586            $rv{$found_key} = $found_val;
587        } else {
588            $found_key = '';
589        }
590    } while($found_key ne '');
591    return %rv;
592}
593
594sub get {
595    my $self = shift;
596    my $key  = shift;
597    my ($rv, $cas) = $self->_do_command(::CMD_GET, $key, '', '');
598
599    my $header = substr $rv, 0, 4, '';
600    my $flags  = unpack("N", $header);
601
602    return ($flags, $rv, $cas);
603}
604
605sub get_multi {
606    my $self = shift;
607    my @keys = @_;
608
609    for (my $i = 0; $i < @keys; $i++) {
610        $self->send_command(::CMD_GETQ, $keys[$i], '', $i, '', 0);
611    }
612
613    my $terminal = @keys + 10;
614    $self->send_command(::CMD_NOOP, '', '', $terminal);
615
616    my %return;
617    while (1) {
618        my ($opaque, $data) = $self->_handle_single_response;
619        last if $opaque == $terminal;
620
621        my $header = substr $data, 0, 4, '';
622        my $flags  = unpack("N", $header);
623
624        $return{$keys[$opaque]} = [$flags, $data];
625    }
626
627    return %return if wantarray;
628    return \%return;
629}
630
631sub version {
632    my $self = shift;
633    return $self->_do_command(::CMD_VERSION, '', '');
634}
635
636sub flush {
637    my $self = shift;
638    return $self->_do_command(::CMD_FLUSH, '', '');
639}
640
641sub add {
642    my $self = shift;
643    my ($key, $val, $flags, $expire) = @_;
644    my $extra_header = pack "NN", $flags, $expire;
645    my $cas = 0;
646    return $self->_do_command(::CMD_ADD, $key, $val, $extra_header, $cas);
647}
648
649sub set {
650    my $self = shift;
651    my ($key, $val, $flags, $expire, $cas) = @_;
652    my $extra_header = pack "NN", $flags, $expire;
653    return $self->_do_command(::CMD_SET, $key, $val, $extra_header, $cas);
654}
655
656sub _append_prepend {
657    my $self = shift;
658    my ($cmd, $key, $val, $cas) = @_;
659    return $self->_do_command($cmd, $key, $val, '', $cas);
660}
661
662sub replace {
663    my $self = shift;
664    my ($key, $val, $flags, $expire) = @_;
665    my $extra_header = pack "NN", $flags, $expire;
666    my $cas = 0;
667    return $self->_do_command(::CMD_REPLACE, $key, $val, $extra_header, $cas);
668}
669
670sub delete {
671    my $self = shift;
672    my ($key) = @_;
673    return $self->_do_command(::CMD_DELETE, $key, '');
674}
675
676sub incr {
677    my $self = shift;
678    my ($key, $amt, $init, $exp) = @_;
679    $amt = 1 unless defined $amt;
680    $init = 0 unless defined $init;
681    $exp = 0 unless defined $exp;
682
683    return $self->_incrdecr(::CMD_INCR, $key, $amt, $init, $exp);
684}
685
686sub decr {
687    my $self = shift;
688    my ($key, $amt, $init, $exp) = @_;
689    $amt = 1 unless defined $amt;
690    $init = 0 unless defined $init;
691    $exp = 0 unless defined $exp;
692
693    return $self->_incrdecr(::CMD_DECR, $key, $amt, $init, $exp);
694}
695
696sub noop {
697    my $self = shift;
698    return $self->_do_command(::CMD_NOOP, '', '');
699}
700
701package MC::Error;
702
703use strict;
704use warnings;
705
706use constant ERR_UNKNOWN_CMD  => 0x81;
707use constant ERR_NOT_FOUND    => 0x1;
708use constant ERR_EXISTS       => 0x2;
709use constant ERR_TOO_BIG      => 0x3;
710use constant ERR_EINVAL       => 0x4;
711use constant ERR_NOT_STORED   => 0x5;
712use constant ERR_DELTA_BADVAL => 0x6;
713
714use overload '""' => sub {
715    my $self = shift;
716    return "Memcache Error ($self->[0]): $self->[1]";
717};
718
719sub new {
720    my $class = shift;
721    my $error = [@_];
722    my $self = bless $error, (ref $class || $class);
723
724    return $self;
725}
726
727sub not_found {
728    my $self = shift;
729    return $self->[0] == ERR_NOT_FOUND;
730}
731
732sub exists {
733    my $self = shift;
734    return $self->[0] == ERR_EXISTS;
735}
736
737sub too_big {
738    my $self = shift;
739    return $self->[0] == ERR_TOO_BIG;
740}
741
742sub delta_badval {
743    my $self = shift;
744    return $self->[0] == ERR_DELTA_BADVAL;
745}
746
747# vim: filetype=perl
748
749