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