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