1<?php 2require_once('CouchbaseTestCase.php'); 3 4class BucketTest extends CouchbaseTestCase { 5 6 /** 7 * Test that connections with invalid details fail. 8 */ 9 function testBadPass() { 10 $h = new \Couchbase\Cluster($this->testDsn); 11 12 $this->wrapException(function() use($h) { 13 if ($this->serverVersion() >= 5.0) { 14 $auth = new \Couchbase\PasswordAuthenticator(); 15 $auth->username($this->testBucket)->password('bad_pass'); 16 } else { 17 $auth = new \Couchbase\ClassicAuthenticator(); 18 $auth->bucket($this->testBucket, 'bad_pass'); 19 } 20 $h->authenticate($auth); 21 $h->openBucket('default'); 22 }, '\Couchbase\Exception', 2); 23 } 24 25 /** 26 * Test that connections with invalid details fail. 27 */ 28 function testBadBucket() { 29 $h = new \Couchbase\Cluster($this->testDsn); 30 $h->authenticate($this->testAuthenticator); 31 32 $this->wrapException(function() use($h) { 33 $h->openBucket('bad_bucket'); 34 }, '\Couchbase\Exception'); 35 } 36 37 /** 38 * Test that a connection with accurate details works. 39 */ 40 function testConnect() { 41 $h = new \Couchbase\Cluster($this->testDsn); 42 $h->authenticate($this->testAuthenticator); 43 $b = $h->openBucket($this->testBucket); 44 $this->setTimeouts($b); 45 return $b; 46 } 47 48 /** 49 * Test basic upsert 50 * 51 * @depends testConnect 52 */ 53 function testBasicUpsert($b) { 54 $key = $this->makeKey('basicUpsert'); 55 56 $res = $b->upsert($key, 'bob'); 57 58 $this->assertValidMetaDoc($res, 'cas'); 59 60 return $key; 61 } 62 63 /** 64 * Test basic get 65 * 66 * @depends testConnect 67 * @depends testBasicUpsert 68 */ 69 function testBasicGet($b, $key) { 70 $res = $b->get($key); 71 72 $this->assertValidMetaDoc($res, 'value', 'cas', 'flags'); 73 $this->assertEquals($res->value, 'bob'); 74 75 return $key; 76 } 77 78 /** 79 * Test basic remove 80 * 81 * @depends testConnect 82 * @depends testBasicGet 83 */ 84 function testBasicRemove($b, $key) { 85 $res = $b->remove($key); 86 87 $this->assertValidMetaDoc($res, 'cas'); 88 89 // This should throw a not-found exception 90 $this->wrapException(function() use($b, $key) { 91 $b->get($key); 92 }, '\Couchbase\Exception', COUCHBASE_KEYNOTFOUND); 93 } 94 95 /** 96 * Test blank string storage and retrieval 97 * 98 * @depends testConnect 99 */ 100 function testBlankStringTrans($b) { 101 $key = $this->makeKey('blankStringTrans'); 102 103 $res = $b->upsert($key, ''); 104 $this->assertValidMetaDoc($res, 'cas'); 105 106 $res = $b->get($key); 107 $this->assertValidMetaDoc($res, 'cas', 'flags'); 108 $this->assertEquals($res->value, ''); 109 } 110 111 /** 112 * Test handing NULL values 113 * 114 * @depends testConnect 115 */ 116 function testEmptyNullValues($b) { 117 $key = $this->makeKey('nullValue'); 118 $res = $b->upsert($key, null); 119 $this->assertValidMetaDoc($res, 'cas'); 120 $res = $b->get($key); 121 $this->assertValidMetaDoc($res, 'cas', 'flags'); 122 $this->assertEquals(null, $res->value); 123 } 124 125 /** 126 * Test multi upsert 127 * 128 * @depends testConnect 129 */ 130 function testMultiUpsert($b) { 131 $keys = array( 132 $this->makeKey('multiUpsert'), 133 $this->makeKey('multiUpsert') 134 ); 135 136 $res = $b->upsert(array( 137 $keys[0] => array('value'=>'joe'), 138 $keys[1] => array('value'=>'jack') 139 )); 140 141 $this->assertCount(2, $res); 142 $this->assertValidMetaDoc($res[$keys[0]], 'cas'); 143 $this->assertValidMetaDoc($res[$keys[1]], 'cas'); 144 145 return $keys; 146 } 147 148 /** 149 * Test multi upsert with the same keys 150 * 151 * @depends testConnect 152 */ 153 function testMultiUpsertTheSameKey($b) { 154 $key = $this->makeKey('multiUpsert'); 155 $keys = array($key, $key); 156 157 $res = $b->upsert(array( 158 $keys[0] => array('value'=>'joe'), 159 $keys[1] => array('value'=>'jack') 160 )); 161 162 $this->assertCount(1, $res); 163 $this->assertValidMetaDoc($res[$keys[0]], 'cas'); 164 $this->assertValidMetaDoc($res[$keys[1]], 'cas'); 165 $this->assertEquals($res[$keys[0]]->cas, $res[$keys[1]]->cas); 166 167 return $keys; 168 } 169 170 /** 171 * Test multi get 172 * 173 * @depends testConnect 174 * @depends testMultiUpsert 175 */ 176 function testMultiGet($b, $keys) { 177 $res = $b->get($keys); 178 179 $this->assertCount(2, $res); 180 $this->assertValidMetaDoc($res[$keys[0]], 'value', 'flags', 'cas'); 181 $this->assertEquals($res[$keys[0]]->value, 'joe'); 182 $this->assertValidMetaDoc($res[$keys[1]], 'value', 'flags', 'cas'); 183 $this->assertEquals($res[$keys[1]]->value, 'jack'); 184 185 return $keys; 186 } 187 188 /** 189 * Test multi remove 190 * 191 * @depends testConnect 192 * @depends testMultiGet 193 */ 194 function testMultiRemove($b, $keys) { 195 $res = $b->remove($keys); 196 197 $this->assertCount(2, $res); 198 $this->assertValidMetaDoc($res[$keys[0]], 'cas'); 199 $this->assertValidMetaDoc($res[$keys[1]], 'cas'); 200 201 // This should throw a not-found exception 202 $res = $b->get($keys); 203 204 $this->assertCount(2, $res); 205 206 // TODO: Different exceptions here might make sense. 207 $this->assertErrorMetaDoc($res[$keys[0]], '\Couchbase\Exception', COUCHBASE_KEYNOTFOUND); 208 $this->assertErrorMetaDoc($res[$keys[1]], '\Couchbase\Exception', COUCHBASE_KEYNOTFOUND); 209 } 210 211 /** 212 * Test basic counter operations w/ an initial value 213 * 214 * @depends testConnect 215 */ 216 function testCounterInitial($b) { 217 $key = $this->makeKey('counterInitial'); 218 219 $res = $b->counter($key, +1, array('initial'=>1)); 220 $this->assertValidMetaDoc($res, 'value', 'cas'); 221 $this->assertEquals(1, $res->value); 222 223 $res = $b->counter($key, +1); 224 $this->assertValidMetaDoc($res, 'value', 'cas'); 225 $this->assertEquals(2, $res->value); 226 227 $res = $b->counter($key, -1); 228 $this->assertValidMetaDoc($res, 'value', 'cas'); 229 $this->assertEquals(1, $res->value); 230 231 $b->remove($key); 232 } 233 234 /** 235 * @test 236 * Test counter operations on missing keys 237 * 238 * @depends testConnect 239 */ 240 function testCounterBadKey($b) { 241 $key = $this->makeKey('counterBadKey'); 242 243 $this->wrapException(function() use($b, $key) { 244 $b->counter($key); 245 }, '\Couchbase\Exception', COUCHBASE_KEYNOTFOUND); 246 247 $this->wrapException(function() use($b, $key) { 248 $b->counter($key, +1); 249 }, '\Couchbase\Exception', COUCHBASE_KEYNOTFOUND); 250 251 $res = $b->counter($key, -1, ['initial' => 42]); 252 $this->assertValidMetaDoc($res, 'cas'); 253 } 254 255 /** 256 * Test expiry operations on keys 257 * 258 * @depends testConnect 259 */ 260 function testExpiry($b) { 261 $key = $this->makeKey('expiry'); 262 263 $b->upsert($key, 'dog', array('expiry' => 1)); 264 265 sleep(2); 266 267 $this->wrapException(function() use($b, $key) { 268 $b->get($key); 269 }, '\Couchbase\Exception', COUCHBASE_KEYNOTFOUND); 270 } 271 272 /** 273 * Test CAS works 274 * 275 * @depends testConnect 276 */ 277 function testCas($b) { 278 $key = $this->makeKey('cas'); 279 280 $res = $b->upsert($key, 'dog'); 281 $this->assertValidMetaDoc($res, 'cas'); 282 $old_cas = $res->cas; 283 284 $res = $b->upsert($key, 'cat'); 285 $this->assertValidMetaDoc($res, 'cas'); 286 287 $this->wrapException(function() use($b, $key, $old_cas) { 288 $b->replace($key, 'ferret', array('cas'=>$old_cas)); 289 }, '\Couchbase\Exception', COUCHBASE_KEYALREADYEXISTS); 290 } 291 292 /** 293 * Test Locks work 294 * 295 * @depends testConnect 296 */ 297 function testLocks($b) { 298 $key = $this->makeKey('locks'); 299 300 $res = $b->upsert($key, 'jog'); 301 $this->assertValidMetaDoc($res, 'cas'); 302 303 // lock for 10 seconds 304 $res = $b->getAndLock($key, 10); 305 $this->assertValidMetaDoc($res, 'value', 'flags', 'cas'); 306 $lockedCas = $res->cas; 307 308 $this->wrapException(function() use($b, $key) { 309 // key is not accessible for locking 310 $b->getAndLock($key, 1); 311 }, '\Couchbase\Exception', COUCHBASE_TMPFAIL); 312 313 $res = $b->unlock($key, ['cas' => $lockedCas]); 314 315 // accessible for locking again 316 $res = $b->getAndLock($key, 1); 317 $this->assertValidMetaDoc($res, 'value', 'flags', 'cas'); 318 } 319 320 /** 321 * Test big upserts 322 * 323 * @depends testConnect 324 */ 325 function testBigUpsert($b) { 326 $key = $this->makeKey('bigUpsert'); 327 328 // $v = str_repeat("*", 0x1000000); 329 $v = str_repeat("*", 0x100000); 330 $res = $b->upsert($key, $v); 331 332 $this->assertValidMetaDoc($res, 'cas'); 333 334 $b->remove($key); 335 336 return $key; 337 } 338 339 /** 340 * @test 341 * Test upsert with no key specified 342 * 343 * @depends testConnect 344 */ 345 function testNoKeyUpsert($b) { 346 $this->wrapException(function() use($b) { 347 $b->upsert('', 'joe'); 348 }, '\Couchbase\Exception', COUCHBASE_EMPTY_KEY); 349 } 350 351 /** 352 * @test 353 * Test recursive transcoder functions 354 */ 355 function testRecursiveTranscode() { 356 global $recursive_transcoder_bucket; 357 global $recursive_transcoder_key2; 358 global $recursive_transcoder_key3; 359 360 $h = new \Couchbase\Cluster($this->testDsn); 361 $h->authenticate($this->testAuthenticator); 362 $b = $h->openBucket($this->testBucket); 363 $this->setTimeouts($b); 364 365 $key1 = $this->makeKey('basicUpsertKey1'); 366 $key2 = $this->makeKey('basicUpsertKey2'); 367 $key3 = $this->makeKey('basicUpsertKey3'); 368 369 // Set up a transcoder that upserts key2 when it sees key1 370 $recursive_transcoder_bucket = $b; 371 $recursive_transcoder_key2 = $key2; 372 $recursive_transcoder_key3 = $key3; 373 $b->setTranscoder( 374 'recursive_transcoder_encoder', // defined at bottom of file 375 'recursive_transcoder_decoder'); // defined at bottom of file 376 377 // Upsert key1, transcoder should set key2 378 $res = $b->upsert($key1, 'key1'); 379 $this->assertValidMetaDoc($res, 'cas'); 380 381 // Check key1 was upserted 382 $res = $b->get($key1); 383 $this->assertValidMetaDoc($res, 'cas'); 384 $this->assertEquals($res->value, 'key1'); 385 386 // Check key2 was upserted, trasncoder should set key3 387 $res = $b->get($key2); 388 $this->assertValidMetaDoc($res, 'cas'); 389 $this->assertEquals($res->value, 'key2'); 390 391 // Check key3 was upserted 392 $res = $b->get($key3); 393 $this->assertValidMetaDoc($res, 'cas'); 394 $this->assertEquals($res->value, 'key3'); 395 } 396 397 /** 398 * Test all option values to make sure they save/load 399 * We open a new bucket for this test to make sure our settings 400 * changes do not affect later tests. Console log level option used 401 * to generate unique (for these test suites) connection string, 402 * so that the lcb_t won't be reused from the pool 403 */ 404 function testOptionVals() { 405 $h = new \Couchbase\Cluster($this->testDsn . "?console_log_level=42"); 406 $h->authenticate($this->testAuthenticator); 407 $b = $h->openBucket($this->testBucket); 408 409 $checkVal = 50243; 410 411 $b->operationTimeout = $checkVal; 412 $b->viewTimeout = $checkVal; 413 $b->durabilityInterval = $checkVal; 414 $b->durabilityTimeout = $checkVal; 415 $b->httpTimeout = $checkVal; 416 $b->configTimeout = $checkVal; 417 $b->configDelay = $checkVal; 418 $b->configNodeTimeout = $checkVal; 419 $b->htconfigIdleTimeout = $checkVal; 420 $b->configPollInterval = $checkVal; 421 422 $this->assertEquals($b->operationTimeout, $checkVal); 423 $this->assertEquals($b->viewTimeout, $checkVal); 424 $this->assertEquals($b->durabilityInterval, $checkVal); 425 $this->assertEquals($b->durabilityTimeout, $checkVal); 426 $this->assertEquals($b->httpTimeout, $checkVal); 427 $this->assertEquals($b->configTimeout, $checkVal); 428 $this->assertEquals($b->configDelay, $checkVal); 429 $this->assertEquals($b->configNodeTimeout, $checkVal); 430 $this->assertEquals($b->htconfigIdleTimeout, $checkVal); 431 $this->assertEquals($b->configPollInterval, $checkVal); 432 } 433 434 /** 435 * @depends testConnect 436 */ 437 function testLookupInFulldoc($b) { 438 if ($this->serverVersion() < 5) { 439 $this->markTestSkipped("Subdoc FullDocument not available for {$this->serverVersion()}"); 440 } 441 $key = $this->makeKey('lookup_in_fulldoc'); 442 $b->upsert($key, ['path1' => 42, 'path2' => 'foo']); 443 444 $result = $b->lookupIn($key) 445 ->get() 446 ->get('path2') 447 ->execute(); 448 $this->assertNull($result->error); 449 $this->assertEquals(2, count($result->value)); 450 $this->assertEquals(COUCHBASE_SUCCESS, $result->value[0]['code']); 451 $this->assertEquals(['path1' => 42, 'path2' => 'foo'], $result->value[0]['value']); 452 $this->assertEquals(COUCHBASE_SUCCESS, $result->value[1]['code']); 453 $this->assertEquals('foo', $result->value[1]['value']); 454 } 455 456 /** 457 * @depends testConnect 458 */ 459 function testLookupIn($b) { 460 $key = $this->makeKey('lookup_in'); 461 $b->upsert($key, array('path1' => 'value1')); 462 463 $result = $b->retrieveIn($key, 'path1'); 464 $this->assertEquals(1, count($result->value)); 465 $this->assertEquals('value1', $result->value[0]['value']); 466 $this->assertEquals(COUCHBASE_SUCCESS, $result->value[0]['code']); 467 $this->assertNotEmpty($result->cas); 468 469 # Try when path is not found 470 $result = $b->retrieveIn($key, 'path2'); 471 $this->assertInstanceOf('\Couchbase\Exception', $result->error); 472 $this->assertEquals(COUCHBASE_SUBDOC_MULTI_FAILURE, $result->error->getCode()); 473 $this->assertEquals(1, count($result->value)); 474 $this->assertEquals(null, $result->value[0]['value']); 475 $this->assertEquals(COUCHBASE_SUBDOC_PATH_ENOENT, $result->value[0]['code']); 476 477 # Try when there is a mismatch 478 $result = $b->retrieveIn($key, 'path1[0]'); 479 $this->assertInstanceOf('\Couchbase\Exception', $result->error); 480 $this->assertEquals(COUCHBASE_SUBDOC_MULTI_FAILURE, $result->error->getCode()); 481 $this->assertEquals(1, count($result->value)); 482 $this->assertEquals(null, $result->value[0]['value']); 483 $this->assertEquals(COUCHBASE_SUBDOC_PATH_MISMATCH, $result->value[0]['code']); 484 485 # Try existence 486 $result = $b->lookupIn($key)->exists('path1')->execute(); 487 $this->assertEquals(1, count($result->value)); 488 $this->assertEquals(COUCHBASE_SUCCESS, $result->value[0]['code']); 489 490 # Not found 491 $result = $b->lookupIn($key)->exists('p')->execute(); 492 $this->assertInstanceOf('\Couchbase\Exception', $result->error); 493 $this->assertEquals(COUCHBASE_SUBDOC_MULTI_FAILURE, $result->error->getCode()); 494 $this->assertEquals(1, count($result->value)); 495 $this->assertEquals(COUCHBASE_SUBDOC_PATH_ENOENT, $result->value[0]['code']); 496 497 498 # Insert a non-JSON document 499 $key = $this->makeKey('lookup_in_nonjson'); 500 $b->upsert($key, 'value'); 501 502 $result = $b->lookupIn($key)->exists('path')->execute(); 503 $this->assertEquals(1, count($result->value)); 504 $this->assertEquals(COUCHBASE_SUBDOC_DOC_NOTJSON, $result->value[0]['code']); 505 506 # Try on non-existing document. Should fail 507 $key = $this->makeKey('lookup_in_with_missing_key'); 508 $result = $b->lookupIn($key)->exists('path')->execute(); 509 $this->assertEquals(NULL, $result->value); 510 $this->assertInstanceOf('\Couchbase\Exception', $result->error); 511 $this->assertEquals(COUCHBASE_KEY_ENOENT, $result->error->getCode()); 512 } 513 514 /** 515 * @depends testConnect 516 */ 517 function testGetCount($b) { 518 if ($this->serverVersion() < 5) { 519 $this->markTestSkipped("Subdoc GET_COUNT not available for {$this->serverVersion()}"); 520 } 521 522 $key = $this->makeKey('get_count'); 523 $b->upsert($key, ['list' => [1, 2, 42], 'object' => ['foo' => 42]]); 524 525 # Try existence 526 $result = $b->lookupIn($key) 527 ->getCount('list') 528 ->getCount('object') 529 ->execute(); 530 $this->assertEquals(2, count($result->value)); 531 $this->assertEquals(COUCHBASE_SUCCESS, $result->value[0]['code']); 532 $this->assertEquals(3, $result->value[0]['value']); 533 $this->assertEquals(COUCHBASE_SUCCESS, $result->value[1]['code']); 534 $this->assertEquals(1, $result->value[1]['value']); 535 } 536 537 /** 538 * @expectedException \Couchbase\Exception 539 * @expectedExceptionMessageRegExp /EMPTY_PATH/ 540 * @depends testConnect 541 */ 542 function testLookupInWithEmptyPath($b) { 543 $key = $this->makeKey('lookup_in_with_empty_path'); 544 $b->upsert($key, array('path1' => 'value1')); 545 546 $result = $b->lookupIn($key)->exists('')->execute(); 547 } 548 549 /** 550 * @depends testConnect 551 */ 552 function testMutateInWithExpiry($b) { 553 $key = $this->makeKey('mutate_in_with_expiry'); 554 $key = 'mutate_in_with_expiry'; 555 $b->upsert($key, new stdClass()); 556 $result = $b->mutateIn($key) 557 ->withExpiry(1) // should expire in one second 558 ->upsert('foo', 'bar') 559 ->execute(); 560 $this->assertNull($result->error); 561 562 sleep(2); 563 564 $this->wrapException(function() use($b, $key) { 565 $res = $b->get($key); 566 var_dump($res); 567 var_dump($b); 568 }, '\Couchbase\Exception', COUCHBASE_KEYNOTFOUND); 569 } 570 571 /** 572 * @depends testConnect 573 */ 574 function testMutateInFulldoc($b) { 575 if ($this->serverVersion() < 5) { 576 $this->markTestSkipped("Subdoc FullDocument not available for {$this->serverVersion()}"); 577 } 578 $key = $this->makeKey('mutate_in_fulldoc'); 579 580 $result = $b->mutateIn($key) 581 ->upsert('created_at', time(), ['xattr' => true, 'createPath' => true]) 582 ->upsert(["new" => "yes"]) 583 ->modeDocument(\Couchbase\MutateInBuilder::FULLDOC_UPSERT) 584 ->execute(); 585 $this->assertNull($result->error); 586 $result = $b->retrieveIn($key, 'new'); 587 $this->assertEquals("yes", $result->value[0]['value']); 588 589 $result = $b->mutateIn($key) 590 ->upsert('created_at', time(), ['xattr' => true, 'createPath' => true]) 591 ->upsert(["duplicate" => "yes"]) 592 ->modeDocument(\Couchbase\MutateInBuilder::FULLDOC_INSERT) 593 ->execute(); 594 $this->assertNotNull($result->error); 595 $this->assertEquals(COUCHBASE_KEY_EEXISTS, $result->error->getCode()); 596 597 $result = $b->mutateIn($key) 598 ->upsert('updated_at', time(), ['xattr' => true, 'createPath' => true]) 599 ->upsert(["updated" => "yes"]) 600 ->modeDocument(\Couchbase\MutateInBuilder::FULLDOC_REPLACE) 601 ->execute(); 602 $this->assertNull($result->error); 603 $result = $b->retrieveIn($key, 'updated'); 604 $this->assertEquals("yes", $result->value[0]['value']); 605 } 606 607 /** 608 * @depends testConnect 609 */ 610 function testMutateIn($b) { 611 $key = $this->makeKey('mutate_in'); 612 $b->upsert($key, new stdClass()); 613 614 $result = $b->mutateIn($key)->upsert('newDict', array('hello'))->execute(); 615 $this->assertNull($result->error); 616 $result = $b->retrieveIn($key, 'newDict'); 617 $this->assertEquals(array('hello'), $result->value[0]['value']); 618 619 # Does not create deep path without create_parents 620 $result = $b->mutateIn($key)->upsert('path.with.missing.parents', 'value')->execute(); 621 $this->assertEquals(COUCHBASE_SUBDOC_MULTI_FAILURE, $result->error->getCode()); 622 $this->assertEquals(1, count($result->value)); 623 $this->assertEquals(COUCHBASE_SUBDOC_PATH_ENOENT, $result->value[0]['code']); 624 625 # Creates deep path without create_parents 626 $result = $b->mutateIn($key)->upsert('path.with.missing.parents', 'value', true)->execute(); 627 $this->assertNull($result->error); 628 $result = $b->retrieveIn($key, 'path.with.missing.parents'); 629 $this->assertEquals('value', $result->value[0]['value']); 630 631 $this->assertNotEmpty($result->cas); 632 $cas = $result->cas; 633 634 $result = $b->mutateIn($key, $cas . 'X')->upsert('newDict', 'withWrongCAS')->execute(); 635 $this->assertEquals(COUCHBASE_KEY_EEXISTS, $result->error->getCode()); 636 $this->assertEquals(NULL, $result->value); 637 # once again with correct CAS 638 $result = $b->mutateIn($key, $cas)->upsert('newDict', 'withCorrectCAS')->execute(); 639 $this->assertNull($result->error); 640 $result = $b->retrieveIn($key, 'newDict'); 641 $this->assertEquals('withCorrectCAS', $result->value[0]['value']); 642 643 # insert into existing path should fail 644 $result = $b->mutateIn($key)->insert('newDict', array('foo' => 42))->execute(); 645 $this->assertEquals(COUCHBASE_SUBDOC_MULTI_FAILURE, $result->error->getCode()); 646 $this->assertEquals(1, count($result->value)); 647 $this->assertEquals(COUCHBASE_SUBDOC_PATH_EEXISTS, $result->value[0]['code']); 648 649 # insert into new path should succeed 650 $result = $b->mutateIn($key)->insert('anotherDict', array('foo' => 42))->execute(); 651 $this->assertNull($result->error); 652 $result = $b->retrieveIn($key, 'anotherDict'); 653 $this->assertEquals(array('foo' => 42), $result->value[0]['value']); 654 655 # replace of existing path should not fail 656 $result = $b->mutateIn($key)->replace('newDict', array(42 => 'foo'))->execute(); 657 $this->assertNull($result->error); 658 $result = $b->retrieveIn($key, 'newDict'); 659 $this->assertEquals(array(42 => 'foo'), $result->value[0]['value']); 660 661 # replace of missing path should not fail 662 $result = $b->mutateIn($key)->replace('missingDict', array(42 => 'foo'))->execute(); 663 $this->assertEquals(COUCHBASE_SUBDOC_MULTI_FAILURE, $result->error->getCode()); 664 $this->assertEquals(1, count($result->value)); 665 $this->assertEquals(COUCHBASE_SUBDOC_PATH_ENOENT, $result->value[0]['code']); 666 667 $result = $b->mutateIn($key)->upsert('empty', '')->execute(); 668 $this->assertNull($result->error); 669 $result = $b->retrieveIn($key, 'empty'); 670 $this->assertEquals('', $result->value[0]['value']); 671 672 $result = $b->mutateIn($key)->upsert('null', null)->execute(); 673 $this->assertNull($result->error); 674 $result = $b->retrieveIn($key, 'null'); 675 $this->assertEquals(null, $result->value[0]['value']); 676 677 $result = $b->mutateIn($key)->upsert('array', array(1, 2, 3))->execute(); 678 $this->assertNull($result->error); 679 680 $result = $b->mutateIn($key)->upsert('array.newKey', 'newVal')->execute(); 681 $this->assertEquals(COUCHBASE_SUBDOC_MULTI_FAILURE, $result->error->getCode()); 682 $this->assertEquals(1, count($result->value)); 683 $this->assertEquals(COUCHBASE_SUBDOC_PATH_MISMATCH, $result->value[0]['code']); 684 685 $result = $b->mutateIn($key)->upsert('array[0]', 'newVal')->execute(); 686 $this->assertEquals(COUCHBASE_SUBDOC_MULTI_FAILURE, $result->error->getCode()); 687 $this->assertEquals(1, count($result->value)); 688 $this->assertEquals(COUCHBASE_SUBDOC_PATH_EINVAL, $result->value[0]['code']); 689 690 $result = $b->mutateIn($key)->upsert('array[3].bleh', 'newVal')->execute(); 691 $this->assertEquals(COUCHBASE_SUBDOC_MULTI_FAILURE, $result->error->getCode()); 692 $this->assertEquals(1, count($result->value)); 693 $this->assertEquals(COUCHBASE_SUBDOC_PATH_ENOENT, $result->value[0]['code']); 694 } 695 696 /** 697 * @expectedException \Couchbase\Exception 698 * @expectedExceptionMessageRegExp /EMPTY_PATH/ 699 * @depends testConnect 700 */ 701 function testMutationInWithEmptyPath($b) { 702 $key = $this->makeKey('lookup_in_with_empty_path'); 703 $b->upsert($key, array('path1' => 'value1')); 704 705 $result = $b->mutateIn($key)->upsert('', 'value')->execute(); 706 } 707 708 /** 709 * @depends testConnect 710 */ 711 function testCounterIn($b) { 712 $key = $this->makeKey('mutate_in'); 713 $b->upsert($key, new stdClass()); 714 715 $result = $b->mutateIn($key)->counter('counter', 100)->execute(); 716 $this->assertNull($result->error); 717 $this->assertNotEmpty($result->cas); 718 $this->assertEquals(1, count($result->value)); 719 $this->assertEquals(100, $result->value[0]['value']); 720 721 $result = $b->mutateIn($key)->upsert('not_a_counter', 'foobar')->execute(); 722 $this->assertNull($result->error); 723 724 $result = $b->mutateIn($key)->counter('not_a_counter', 100)->execute(); 725 $this->assertEquals(COUCHBASE_SUBDOC_MULTI_FAILURE, $result->error->getCode()); 726 $this->assertEquals(1, count($result->value)); 727 $this->assertEquals(COUCHBASE_SUBDOC_PATH_MISMATCH, $result->value[0]['code']); 728 729 $result = $b->mutateIn($key)->counter('path.to.new.counter', 100)->execute(); 730 $this->assertEquals(COUCHBASE_SUBDOC_MULTI_FAILURE, $result->error->getCode()); 731 $this->assertEquals(1, count($result->value)); 732 $this->assertEquals(COUCHBASE_SUBDOC_PATH_ENOENT, $result->value[0]['code']); 733 734 $result = $b->mutateIn($key)->counter('path.to.new.counter', 100, true)->execute(); 735 $this->assertNull($result->error); 736 $this->assertNotEmpty($result->cas); 737 $this->assertEquals(1, count($result->value)); 738 $this->assertEquals(100, $result->value[0]['value']); 739 740 $result = $b->mutateIn($key)->counter('counter', -25)->execute(); 741 $this->assertNull($result->error); 742 $this->assertNotEmpty($result->cas); 743 $this->assertEquals(1, count($result->value)); 744 $this->assertEquals(75, $result->value[0]['value']); 745 746 $result = $b->mutateIn($key)->counter('counter', 0)->execute(); 747 $this->assertEquals(COUCHBASE_SUBDOC_MULTI_FAILURE, $result->error->getCode()); 748 $this->assertEquals(1, count($result->value)); 749 $this->assertEquals(COUCHBASE_SUBDOC_BAD_DELTA, $result->value[0]['code']); 750 } 751 752 /** 753 * @depends testConnect 754 */ 755 function testMultiLookupIn($b) { 756 $key = $this->makeKey('multi_lookup_in'); 757 $b->upsert($key, array( 758 'field1' => 'value1', 759 'field2' => 'value2', 760 'array' => array(1, 2, 3), 761 'boolean' => false, 762 )); 763 764 $result = $b->lookupIn($key) 765 ->get('field1') 766 ->exists('field2') 767 ->exists('field3') 768 ->execute(); 769 770 $this->assertEquals(COUCHBASE_SUBDOC_MULTI_FAILURE, $result->error->getCode()); 771 $this->assertEquals(3, count($result->value)); 772 773 $this->assertEquals(COUCHBASE_SUCCESS, $result->value[0]['code']); 774 $this->assertEquals('value1', $result->value[0]['value']); 775 776 $this->assertEquals(COUCHBASE_SUCCESS, $result->value[1]['code']); 777 $this->assertEquals(null, $result->value[1]['value']); 778 779 $this->assertEquals(COUCHBASE_SUBDOC_PATH_ENOENT, $result->value[2]['code']); 780 $this->assertEquals(null, $result->value[2]['value']); 781 } 782 783 /** 784 * @depends testConnect 785 */ 786 function testMultiMutateIn($b) { 787 $key = $this->makeKey('multi_mutate_in'); 788 $b->upsert($key, array( 789 'field1' => 'value1', 790 'field2' => 'value2', 791 'array' => array(1, 2, 3), 792 )); 793 794 $result = $b->mutateIn($key) 795 ->replace('field1', array('foo' => 'bar')) 796 ->remove('array') 797 ->replace('missing', "hello world") 798 ->execute(); 799 800 $this->assertEquals(COUCHBASE_SUBDOC_MULTI_FAILURE, $result->error->getCode()); 801 $this->assertEquals(1, count($result->value)); 802 $this->assertEquals(COUCHBASE_SUBDOC_PATH_ENOENT, $result->value[2]['code']); 803 } 804 805 /** 806 * @depends testConnect 807 */ 808 function testMultiValue($b) { 809 $key = $this->makeKey('multi_value'); 810 $b->upsert($key, array('array' => array())); 811 812 $result = $b->mutateIn($key)->arrayAppend('array', true)->execute(); 813 $this->assertNull($result->error); 814 815 $result = $b->retrieveIn($key, 'array'); 816 $this->assertNull($result->error); 817 $this->assertEquals(1, count($result->value)); 818 $this->assertEquals(array(true), $result->value[0]['value']); 819 820 $result = $b->mutateIn($key)->arrayAppendAll('array', array(1, 2, 3))->execute(); 821 $this->assertNull($result->error); 822 823 $result = $b->retrieveIn($key, 'array'); 824 $this->assertNull($result->error); 825 $this->assertEquals(1, count($result->value)); 826 $this->assertEquals(array(true, 1, 2, 3), $result->value[0]['value']); 827 828 $result = $b->mutateIn($key)->arrayPrepend('array', array(42))->execute(); 829 $this->assertNull($result->error); 830 831 $result = $b->retrieveIn($key, 'array'); 832 $this->assertNull($result->error); 833 $this->assertEquals(1, count($result->value)); 834 $this->assertEquals(array(array(42), true, 1, 2, 3), $result->value[0]['value']); 835 } 836 837 /** 838 * @depends testConnect 839 */ 840 function testSubdocAttributes($b) { 841 if ($this->serverVersion() < 5) { 842 $this->markTestSkipped("Subdoc attributes are not supported, skipping the test"); 843 } 844 $key = $this->makeKey('subdoc_attributes'); 845 $b->upsert($key, ['foo' => 'bar']); 846 847 $result = $b->mutateIn($key) 848 ->upsert('app.created_by', ['name' => 'John Doe', 'role' => 'DB administrator'], 849 ['xattr' => true, 'createPath' => true]) 850 ->execute(); 851 $this->assertEquals(1, count($result->value)); 852 $this->assertEquals(COUCHBASE_SUCCESS, $result->value[0]['code']); 853 854 $result = $b->lookupIn($key) 855 ->get('app.created_by', ['xattr' => true, 'createPath' => true]) 856 ->execute(); 857 $this->assertEquals(1, count($result->value)); 858 $this->assertEquals(COUCHBASE_SUCCESS, $result->value[0]['code']); 859 $this->assertEquals(['name' => 'John Doe', 'role' => 'DB administrator'], $result->value[0]['value']); 860 } 861} 862 863function libcouchbase_version() { 864 $ext = new ReflectionExtension('couchbase'); 865 ob_start(); 866 $ext->info(); 867 $data = ob_get_contents(); 868 ob_end_clean(); 869 preg_match('/libcouchbase runtime version => (\d+\.\d+\.\d+)/', $data, $matches); 870 return $matches[1]; 871} 872 873function recursive_transcoder_encoder($value) { 874 global $recursive_transcoder_bucket; 875 global $recursive_transcoder_key2; 876 if ($value == 'key1') { 877 $recursive_transcoder_bucket->upsert( 878 $recursive_transcoder_key2, 'key2'); 879 } 880 return couchbase_default_encoder($value); 881} 882 883function recursive_transcoder_decoder($bytes, $flags, $datatype) { 884 global $recursive_transcoder_bucket; 885 global $recursive_transcoder_key3; 886 $value = couchbase_default_decoder($bytes, $flags, $datatype); 887 if ($value == 'key2') { 888 $recursive_transcoder_bucket->upsert( 889 $recursive_transcoder_key3, 'key3'); 890 } 891 return $value; 892} 893