1 /**
2  *     Copyright 2016-2017 Couchbase, Inc.
3  *
4  *   Licensed under the Apache License, Version 2.0 (the "License");
5  *   you may not use this file except in compliance with the License.
6  *   You may obtain a copy of the License at
7  *
8  *       http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *   Unless required by applicable law or agreed to in writing, software
11  *   distributed under the License is distributed on an "AS IS" BASIS,
12  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *   See the License for the specific language governing permissions and
14  *   limitations under the License.
15  */
16 
17 #include "couchbase.h"
18 
19 #define LOGARGS(instance, lvl) LCB_LOG_##lvl, instance, "pcbc/get", __FILE__, __LINE__
20 
21 typedef struct {
22     opcookie_res header;
23     char *key;
24     int key_len;
25     char *bytes;
26     int bytes_len;
27     lcb_U32 flags;
28     lcb_datatype_t datatype;
29     lcb_cas_t cas;
30 } opcookie_get_res;
31 
get_callback(lcb_t instance,int cbtype,const lcb_RESPBASE * rb)32 void get_callback(lcb_t instance, int cbtype, const lcb_RESPBASE *rb)
33 {
34     opcookie_get_res *result = ecalloc(1, sizeof(opcookie_get_res));
35     const lcb_RESPGET *resp = (const lcb_RESPGET *)rb;
36     TSRMLS_FETCH();
37 
38     PCBC_RESP_ERR_COPY(result->header, cbtype, rb);
39     result->key_len = resp->nkey;
40     if (resp->nkey) {
41         result->key = estrndup(resp->key, resp->nkey);
42     }
43     result->bytes_len = resp->nvalue;
44     if (resp->nvalue) {
45         result->bytes = estrndup(resp->value, resp->nvalue);
46     }
47     result->flags = resp->itmflags;
48     result->datatype = resp->datatype;
49     result->cas = resp->cas;
50 
51     opcookie_push((opcookie *)rb->cookie, &result->header);
52 }
53 
proc_get_results(pcbc_bucket_t * bucket,zval * return_value,opcookie * cookie,int is_mapped TSRMLS_DC)54 static lcb_error_t proc_get_results(pcbc_bucket_t *bucket, zval *return_value, opcookie *cookie,
55                                     int is_mapped TSRMLS_DC)
56 {
57     opcookie_get_res *res;
58     lcb_error_t err = LCB_SUCCESS;
59 #ifdef LCB_TRACING
60     lcbtrace_SPAN *parent = cookie->span;
61     lcbtrace_TRACER *tracer = lcb_get_tracer(bucket->conn->lcb);
62 #endif
63 
64     // If we are not mapped, we need to throw any op errors
65     if (is_mapped == 0) {
66         err = opcookie_get_first_error(cookie);
67     }
68 
69     if (err == LCB_SUCCESS) {
70         FOREACH_OPCOOKIE_RES(opcookie_get_res, res, cookie)
71         {
72             zval *doc = bop_get_return_doc(return_value, res->key, res->key_len, is_mapped TSRMLS_CC);
73 
74             if (res->header.err == LCB_SUCCESS) {
75 #ifdef LCB_TRACING
76                 lcbtrace_SPAN *span = NULL;
77                 if (parent) {
78                     lcbtrace_REF ref;
79                     ref.type = LCBTRACE_REF_CHILD_OF;
80                     ref.span = parent;
81                     span = lcbtrace_span_start(tracer, "php/" LCBTRACE_OP_RESPONSE_DECODING, LCBTRACE_NOW, &ref);
82                     lcbtrace_span_add_tag_str(span, LCBTRACE_TAG_COMPONENT, pcbc_client_string);
83                     lcbtrace_span_add_tag_str(span, LCBTRACE_TAG_SERVICE, LCBTRACE_TAG_SERVICE_KV);
84                 }
85 #endif
86                 pcbc_document_init_decode(doc, bucket, res->bytes, res->bytes_len, res->flags, res->datatype, res->cas,
87                                           NULL TSRMLS_CC);
88 #ifdef LCB_TRACING
89                 if (span) {
90                     lcbtrace_span_finish(span, LCBTRACE_NOW);
91                 }
92 #endif
93             } else {
94                 pcbc_document_init_error(doc, &res->header TSRMLS_CC);
95             }
96         }
97     }
98 
99     FOREACH_OPCOOKIE_RES(opcookie_get_res, res, cookie)
100     {
101         if (res->key) {
102             efree(res->key);
103         }
104         if (res->bytes) {
105             efree(res->bytes);
106         }
107         PCBC_RESP_ERR_FREE(res->header);
108     }
109 
110     return err;
111 }
112 
pcbc_bucket_get(pcbc_bucket_t * obj,pcbc_pp_state * pp_state,pcbc_pp_id * id,zval ** lock,zval ** expiry,zval ** groupid,zval * return_value TSRMLS_DC)113 void pcbc_bucket_get(pcbc_bucket_t *obj, pcbc_pp_state *pp_state, pcbc_pp_id *id, zval **lock, zval **expiry,
114                      zval **groupid, zval *return_value TSRMLS_DC)
115 {
116     int ii, ncmds, nscheduled;
117     opcookie *cookie;
118     lcb_error_t err = LCB_SUCCESS;
119 #ifdef LCB_TRACING
120     lcbtrace_TRACER *tracer = NULL;
121 #endif
122 
123     ncmds = pcbc_pp_keycount(pp_state);
124     cookie = opcookie_init();
125 
126 #ifdef LCB_TRACING
127     tracer = lcb_get_tracer(obj->conn->lcb);
128     if (tracer) {
129         cookie->span = lcbtrace_span_start(tracer, "php/" LCBTRACE_OP_GET, 0, NULL);
130         lcbtrace_span_add_tag_str(cookie->span, LCBTRACE_TAG_COMPONENT, pcbc_client_string);
131         lcbtrace_span_add_tag_str(cookie->span, LCBTRACE_TAG_SERVICE, LCBTRACE_TAG_SERVICE_KV);
132     }
133 #endif
134 
135     nscheduled = 0;
136     for (ii = 0; pcbc_pp_next(pp_state); ++ii) {
137         lcb_CMDGET cmd = {0};
138 
139         if (lock) {
140             PCBC_CHECK_ZVAL_LONG(*lock, "lockTime must be an integer");
141         }
142         if (expiry) {
143             PCBC_CHECK_ZVAL_LONG(*expiry, "expiry must be an integer");
144         }
145         if (groupid) {
146             PCBC_CHECK_ZVAL_STRING(*groupid, "groupid must be a string");
147         }
148 
149         LCB_CMD_SET_KEY(&cmd, id->str, id->len);
150 #ifdef LCB_TRACING
151         if (cookie->span) {
152             LCB_CMD_SET_TRACESPAN(&cmd, cookie->span);
153         }
154 #endif
155         if (expiry && *expiry) {
156             cmd.lock = 0;
157             cmd.exptime = Z_LVAL_P(*expiry);
158         } else if (lock && *lock) {
159             cmd.lock = 1;
160             cmd.exptime = Z_LVAL_P(*lock);
161         }
162         if (groupid && *groupid) {
163             LCB_KREQ_SIMPLE(&cmd._hashkey, Z_STRVAL_P(*groupid), Z_STRLEN_P(*groupid));
164         }
165         err = lcb_get3(obj->conn->lcb, cookie, &cmd);
166         if (err != LCB_SUCCESS) {
167             break;
168         }
169 
170         nscheduled++;
171     }
172     pcbc_assert_number_of_commands(obj->conn->lcb, "get", nscheduled, ncmds, err);
173 
174     if (nscheduled) {
175         lcb_wait(obj->conn->lcb);
176         err = proc_get_results(obj, return_value, cookie, pcbc_pp_ismapped(pp_state) TSRMLS_CC);
177     }
178 
179 #ifdef LCB_TRACING
180     if (cookie->span) {
181         lcbtrace_span_finish(cookie->span, LCBTRACE_NOW);
182     }
183 #endif
184     opcookie_destroy(cookie);
185 
186     if (err != LCB_SUCCESS) {
187         throw_lcb_exception(err);
188     }
189 }
190 
191 /* {{{ proto mixed Bucket::get(string $id, array $options) */
PHP_METHOD(Bucket,get)192 PHP_METHOD(Bucket, get)
193 {
194     pcbc_bucket_t *obj = Z_BUCKET_OBJ_P(getThis());
195     pcbc_pp_state pp_state;
196     pcbc_pp_id id;
197     zval *lock = NULL, *expiry = NULL, *groupid = NULL;
198 
199     // Note that groupid is experimental here and should not be used.
200     if (pcbc_pp_begin(ZEND_NUM_ARGS() TSRMLS_CC, &pp_state, "id||lockTime,expiry,groupid", &id, &lock, &expiry,
201                       &groupid) != SUCCESS) {
202         throw_pcbc_exception("Invalid arguments.", LCB_EINVAL);
203         RETURN_NULL();
204     }
205 
206     pcbc_bucket_get(obj, &pp_state, &id, &lock, &expiry, &groupid, return_value TSRMLS_CC);
207 }
208 
209 /* {{{ proto mixed Bucket::getAndLock(string $id, int $lockTime, array $options) */
PHP_METHOD(Bucket,getAndLock)210 PHP_METHOD(Bucket, getAndLock)
211 {
212     pcbc_bucket_t *obj = Z_BUCKET_OBJ_P(getThis());
213     pcbc_pp_state pp_state;
214     pcbc_pp_id id;
215     zval *lock = NULL, *groupid = NULL;
216 
217     // Note that groupid is experimental here and should not be used.
218     if (pcbc_pp_begin(ZEND_NUM_ARGS() TSRMLS_CC, &pp_state, "id,lockTime||groupid", &id, &lock, &groupid) != SUCCESS) {
219         throw_pcbc_exception("Invalid arguments.", LCB_EINVAL);
220         RETURN_NULL();
221     }
222 
223     pcbc_bucket_get(obj, &pp_state, &id, &lock, NULL, &groupid, return_value TSRMLS_CC);
224 }
225 
226 /* {{{ proto mixed Bucket::getAndTouch(string $id, int $expiry, array $options) */
PHP_METHOD(Bucket,getAndTouch)227 PHP_METHOD(Bucket, getAndTouch)
228 {
229     pcbc_bucket_t *obj = Z_BUCKET_OBJ_P(getThis());
230     pcbc_pp_state pp_state;
231     pcbc_pp_id id;
232     zval *expiry = NULL, *groupid = NULL;
233 
234     // Note that groupid is experimental here and should not be used.
235     if (pcbc_pp_begin(ZEND_NUM_ARGS() TSRMLS_CC, &pp_state, "id,expiry||groupid", &id, &expiry, &groupid) != SUCCESS) {
236         throw_pcbc_exception("Invalid arguments.", LCB_EINVAL);
237         RETURN_NULL();
238     }
239 
240     pcbc_bucket_get(obj, &pp_state, &id, NULL, &expiry, &groupid, return_value TSRMLS_CC);
241 }
242 
243 // get($id {, $lock, $groupid}) : MetaDoc
PHP_METHOD(Bucket,getFromReplica)244 PHP_METHOD(Bucket, getFromReplica)
245 {
246     pcbc_bucket_t *obj = Z_BUCKET_OBJ_P(getThis());
247     int ii, ncmds, nscheduled;
248     pcbc_pp_state pp_state;
249     pcbc_pp_id id;
250     zval *zindex, *zgroupid;
251     opcookie *cookie;
252     lcb_error_t err = LCB_SUCCESS;
253 #ifdef LCB_TRACING
254     lcbtrace_TRACER *tracer = NULL;
255 #endif
256 
257     // Note that groupid is experimental here and should not be used.
258     if (pcbc_pp_begin(ZEND_NUM_ARGS() TSRMLS_CC, &pp_state, "id||index,groupid", &id, &zindex, &zgroupid) != SUCCESS) {
259         throw_pcbc_exception("Invalid arguments.", LCB_EINVAL);
260         RETURN_NULL();
261     }
262 
263     ncmds = pcbc_pp_keycount(&pp_state);
264     cookie = opcookie_init();
265 #ifdef LCB_TRACING
266     tracer = lcb_get_tracer(obj->conn->lcb);
267     if (tracer) {
268         cookie->span = lcbtrace_span_start(tracer, "php/" LCBTRACE_OP_GET_FROM_REPLICA, 0, NULL);
269         lcbtrace_span_add_tag_str(cookie->span, LCBTRACE_TAG_COMPONENT, pcbc_client_string);
270         lcbtrace_span_add_tag_str(cookie->span, LCBTRACE_TAG_SERVICE, LCBTRACE_TAG_SERVICE_KV);
271     }
272 #endif
273 
274     nscheduled = 0;
275     for (ii = 0; pcbc_pp_next(&pp_state); ++ii) {
276         lcb_CMDGETREPLICA cmd = {0};
277 
278         PCBC_CHECK_ZVAL_LONG(zindex, "index must be an integer");
279         PCBC_CHECK_ZVAL_STRING(zgroupid, "groupid must be a string");
280 
281         LCB_CMD_SET_KEY(&cmd, id.str, id.len);
282 #ifdef LCB_TRACING
283         if (cookie->span) {
284             LCB_CMD_SET_TRACESPAN(&cmd, cookie->span);
285         }
286 #endif
287         if (zindex) {
288             cmd.index = Z_LVAL_P(zindex);
289             if (cmd.index >= 0) {
290                 cmd.strategy = LCB_REPLICA_SELECT;
291             } else {
292                 cmd.strategy = LCB_REPLICA_FIRST;
293             }
294         }
295         if (zgroupid) {
296             LCB_KREQ_SIMPLE(&cmd._hashkey, Z_STRVAL_P(zgroupid), Z_STRLEN_P(zgroupid));
297         }
298 
299         err = lcb_rget3(obj->conn->lcb, cookie, &cmd);
300         if (err != LCB_SUCCESS) {
301             break;
302         }
303         nscheduled++;
304     }
305     pcbc_assert_number_of_commands(obj->conn->lcb, "get_from_replica", nscheduled, ncmds, err);
306 
307     if (nscheduled) {
308         lcb_wait(obj->conn->lcb);
309 
310         err = proc_get_results(obj, return_value, cookie, pcbc_pp_ismapped(&pp_state) TSRMLS_CC);
311     }
312 
313 #ifdef LCB_TRACING
314     if (cookie->span) {
315         lcbtrace_span_finish(cookie->span, LCBTRACE_NOW);
316     }
317 #endif
318     opcookie_destroy(cookie);
319 
320     if (err != LCB_SUCCESS) {
321         throw_lcb_exception(err);
322     }
323 }
324