Skip to content
This repository was archived by the owner on Apr 20, 2022. It is now read-only.

Commit c30603a

Browse files
bsubhashnitrondn
authored andcommitted
Client wrapper for complete & partial updates
Change-Id: I862baeae86cd7e36707f27cc9c68b9b1f17360ad Reviewed-on: http://review.couchbase.org/26432 Reviewed-by: Trond Norbye <trond.norbye@gmail.com> Tested-by: Trond Norbye <trond.norbye@gmail.com>
1 parent f7b3b70 commit c30603a

File tree

2 files changed

+306
-0
lines changed

2 files changed

+306
-0
lines changed

example/CouchbaseUpdate.php

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
<?php
2+
/**
3+
* This example demonstrates how to update individual fields in a document
4+
* with implicit concurrency handling
5+
*/
6+
class CouchbaseUpdate extends Couchbase {
7+
8+
private $max_retries = 1;
9+
10+
/**
11+
* recurses through the path to find the closest parent
12+
* object of the field to be updated
13+
*/
14+
private function _recursive_update($obj, $path, $update, $depth)
15+
{
16+
$node = $path[$depth];
17+
$next_node = $path[$depth+1];
18+
19+
if(!$obj){
20+
throw new Exception("Field not found");
21+
}
22+
if(!$path || gettype($path) != 'array'){
23+
throw new Exception("Invalid path");
24+
}
25+
26+
/* update at the root level */
27+
if(count($path) == 1){
28+
reset($update);
29+
$k = key($update);
30+
$v = current($update);
31+
32+
if(isset($obj[$k])){
33+
return (object) array_replace_recursive((array) $obj, (array) $update);
34+
}
35+
else{
36+
return (object) array_merge_recursive((array) $obj, (array) $update);
37+
}
38+
}
39+
40+
/**
41+
* special handling for updating arrays at depth 2
42+
* as arrays have to be replaced
43+
*/
44+
if(count($path) == 2){
45+
if(gettype($obj[$path[1]]) == 'array'){
46+
$val = $obj[$path[1]];
47+
$new_val = array_merge_recursive($val, $update);
48+
$obj[$path[1]] = $new_val;
49+
return $obj;
50+
}
51+
}
52+
53+
/**
54+
* similar to special handling above for arrays but at
55+
* a varied depth
56+
*/
57+
if($depth == count($path) - 2){
58+
$temp = (array)$obj[$node];
59+
if(gettype($temp[$next_node]) == 'array'){
60+
reset($update);
61+
$k = key($update);
62+
$v = current($update);
63+
$val = $temp[$next_node];
64+
65+
if(isset($val[$k])){
66+
$new_val = array_replace_recursive($val, $update);
67+
}
68+
else {
69+
$new_val = array_merge_recursive($val, $update);
70+
}
71+
72+
if(gettype($obj[$node]) == 'object'){
73+
$obj[$node]->$next_node = $new_val;
74+
}
75+
else{
76+
$obj[$node][$next_node] = $new_val;
77+
}
78+
return;
79+
}
80+
}
81+
82+
/* handling scalar values and JSON Object */
83+
if($depth == count($path) - 1){
84+
reset($update);
85+
$k = key($update);
86+
$v = current($update);
87+
88+
if(gettype($obj[$node]) == 'object'){
89+
$obj[$node]->$k = $v;
90+
}
91+
else{
92+
$obj[$node][$k] = $v;
93+
}
94+
return;
95+
}
96+
97+
if($depth == 0){
98+
/* ignore "root" */
99+
self::_recursive_update((array)$obj[$next_node], $path, $update, $depth + 2);
100+
}
101+
102+
else{
103+
self::_recursive_update((array)$obj[$node], $path, $update, $depth + 1);
104+
}
105+
return $obj;
106+
}
107+
108+
/* merge updates individually */
109+
private function _merge($doc, $updates){
110+
foreach($updates as $k => $v){
111+
$doc = self::_recursive_update((array)$doc, $v['path'], array($k => $v['val']), 0);
112+
}
113+
return $doc;
114+
}
115+
116+
/**
117+
* Partial Update: Update/Add individual fields in a JSON Document safely
118+
* Complete Update: Create/Replace the entire Document,
119+
* the given object is encoded to JSON
120+
* @param $key Document Identifier
121+
* @param $obj Either Entire Document object for complete update OR
122+
* An array with partial updates as keys where
123+
* key is an index for an array otherwise the field name.
124+
* Each key is associated with path and val properties.
125+
* Path is an array of the field path from the root (doc's parent object) and
126+
* Val is the new value of the field.
127+
* @param $is_partial Boolean to identify a partial update
128+
* @params $cas, $persist_to, $replicate_to function similar to SET operation
129+
* @return Boolean true on SUCCESS else false
130+
*/
131+
132+
public function update($key, $obj, $is_partial = false, $expiry = 0, $persist_to = 0, $replicate_to = 0) {
133+
$counter = 1;
134+
$n_json = NULL;
135+
do{
136+
$v_json = parent::get($key, NULL, $cas);
137+
if($obj){
138+
if($is_partial == true){
139+
if(!$cas){
140+
throw new Exception("No existing document for partial update");
141+
}
142+
$valGet = json_decode($v_json);
143+
$val = self::_merge($valGet, $obj);
144+
$n_json = json_encode($val);
145+
}
146+
else{
147+
$cas = 0;
148+
$n_json = json_encode($obj);
149+
}
150+
}
151+
$rv = parent::set($key, $n_json, $expiry , $cas, $persist_to, $replicate_to);
152+
153+
}while($rv == false && ++$counter <= $this->max_retries);
154+
return $rv;
155+
}
156+
157+
/**
158+
* Set the max retries count
159+
* @param max_retries Positive number
160+
*/
161+
public function setMaxRetries($max_retries){
162+
163+
if(!$max_retries || is_nan($max_retries) || $max_retries <= 0){
164+
throw new Exception("Illegal count for retries");
165+
}
166+
167+
$this->max_retries = $max_retries;
168+
}
169+
}
170+
?>

example/CouchbaseUpdateExample.php

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<?php
2+
/**
3+
* Simple reviews example demonstrating complete & partial updates
4+
* through CouchbaseUpdate, a client wrapper API
5+
* Intro to example - User reviews for a hotel are pushed into an Array in Document
6+
* with some review specific attributes such as stars,likes,hates.. which can take
7+
* valid JSON values. The example demos setting objects, pushing to arrays, updating
8+
* array elements..
9+
*/
10+
require_once 'CouchbaseUpdate.php';
11+
12+
class Hotel {
13+
public $name;
14+
public $reviews;
15+
}
16+
17+
class Review {
18+
public $username;
19+
public $stars;
20+
public $review_critics;
21+
public $reviewed_hotels;
22+
}
23+
24+
class ReviewCritic {
25+
public $thumbs_up;
26+
public $thumbs_down;
27+
}
28+
29+
$cu = new CouchbaseUpdate();
30+
31+
/* retries count specified */
32+
$cu->setMaxRetries(10);
33+
34+
$doc = new Hotel();
35+
$doc->name = "Jane Doe's Hotel";
36+
$doc->reviews = array();
37+
38+
/* delete the existing document */
39+
$cu->delete($doc->name);
40+
41+
/* complete update creates/replaces the document */
42+
$rv = $cu->update($doc->name, $doc);
43+
44+
/**
45+
* partial updates in an array with fields as keys and with following properties
46+
* path: path from the parent where root is doc's parent object
47+
* val: new value to be updated
48+
*/
49+
$r = new Review();
50+
$r->username = "foo";
51+
$r->stars = 5;
52+
$r->review_critics = new ReviewCritic();
53+
$rv = $cu->update($doc->name, array(
54+
0 => array(
55+
"path" => array("root", "reviews"),
56+
"val" => $r),
57+
"review_count" => array(
58+
"path" => array("root"),
59+
"val" => 1)
60+
), true);
61+
62+
/* update individual field */
63+
$rv = $cu->update($doc->name, array(
64+
"username" => array(
65+
"path" => array("root","reviews",0),
66+
"val" => "cool_foo"
67+
)), true);
68+
69+
/* update multiple fields*/
70+
$r = new Review();
71+
$r->username = "bar";
72+
$r->stars = 5;
73+
$rv = $cu->update($doc->name, array(
74+
1 => array(
75+
"path" => array("root", "reviews"),
76+
"val" => $r),
77+
"review_count" => array(
78+
"path" => array("root"),
79+
"val" => 2),
80+
"reviewed_hotels" => array(
81+
"path" => array("root", "reviews", 0),
82+
"val" => array($doc->name)),
83+
"thumbs_up" => array(
84+
"path" => array("root", "reviews", 0, "review_critics"),
85+
"val" => 1)
86+
), true);
87+
88+
/* set NULL */
89+
$rv = $cu->update($doc->name, array(
90+
"prices" => array(
91+
"path" => array("root"),
92+
"val" => NULL)
93+
), true);
94+
95+
/* set boolean */
96+
$rv = $cu->update($doc->name, array(
97+
"is_account_valid" => array(
98+
"path" => array("root", "reviews", 1),
99+
"val" => false)
100+
), true);
101+
$rv = $cu->update($doc->name, array(
102+
"is_account_valid" => array(
103+
"path" => array("root", "reviews", 0),
104+
"val" => true)
105+
), true);
106+
107+
/* set object */
108+
$rv = $cu->update($doc->name, array(
109+
"review_critics" => array(
110+
"path" => array("root", "reviews", 1),
111+
"val" => new ReviewCritic())
112+
), true);
113+
114+
/* set array */
115+
$rv = $cu->update($doc->name, array(
116+
"reviewed_hotels" => array(
117+
"path" => array("root", "reviews", 1),
118+
"val" => array($doc->name))
119+
), true);
120+
121+
/* push to an array */
122+
$rv = $cu->update($doc->name, array(
123+
1 => array(
124+
"path" => array("root", "reviews", 0, "reviewed_hotels"),
125+
"val" => "")
126+
), true);
127+
128+
/* update an array element */
129+
$rv = $cu->update($doc->name, array(
130+
1 => array(
131+
"path" => array("root", "reviews", 0, "reviewed_hotels"),
132+
"val" => "John Doe's hotel")
133+
), true);
134+
135+
print_r(json_decode($cu->get($doc->name)));
136+
?>

0 commit comments

Comments
 (0)