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 #include <Zend/zend_alloc.h>
19 
20 #define LOGARGS(builder, lvl) LCB_LOG_##lvl, builder->bucket->conn->lcb, "pcbc/lookup_in_builder", __FILE__, __LINE__
21 
22 zend_class_entry *pcbc_lookup_in_builder_ce;
23 
24 /* {{{ proto void LookupInBuilder::__construct() Should not be called directly */
PHP_METHOD(LookupInBuilder,__construct)25 PHP_METHOD(LookupInBuilder, __construct)
26 {
27     throw_pcbc_exception("Accessing private constructor.", LCB_EINVAL);
28 }
29 /* }}} */
30 
pcbc_lookup_in_builder_get(pcbc_lookup_in_builder_t * builder,char * path,int path_len,zval * options TSRMLS_DC)31 int pcbc_lookup_in_builder_get(pcbc_lookup_in_builder_t *builder, char *path, int path_len, zval *options TSRMLS_DC)
32 {
33     pcbc_sd_spec_t *spec;
34 
35     spec = ecalloc(1, sizeof(pcbc_sd_spec_t));
36     spec->next = NULL;
37     if (path) {
38         spec->s.sdcmd = LCB_SDCMD_GET;
39         spec->s.options = pcbc_subdoc_options_to_flags(1, 1, options TSRMLS_CC);
40         PCBC_SDSPEC_COPY_PATH(spec, path, path_len);
41     } else {
42         spec->s.sdcmd = LCB_SDCMD_GET_FULLDOC;
43     }
44     if (builder->tail) {
45         builder->tail->next = spec;
46     }
47     builder->tail = spec;
48     if (builder->head == NULL) {
49         builder->head = builder->tail;
50     }
51     builder->nspecs++;
52     return SUCCESS;
53 }
54 
55 /* {{{ proto \Couchbase\LookupInBuilder LookupInBuilder::get(string $path, array $options = []) */
PHP_METHOD(LookupInBuilder,get)56 PHP_METHOD(LookupInBuilder, get)
57 {
58     pcbc_lookup_in_builder_t *obj;
59     char *path = NULL;
60     pcbc_str_arg_size path_len = 0;
61     zval *options = NULL;
62     int rv;
63 
64     rv = zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sz", &path, &path_len, &options);
65     if (rv == FAILURE) {
66         RETURN_NULL();
67     }
68 
69     obj = Z_LOOKUP_IN_BUILDER_OBJ_P(getThis());
70     pcbc_lookup_in_builder_get(obj, path, path_len, options TSRMLS_CC);
71 
72     RETURN_ZVAL(getThis(), 1, 0);
73 } /* }}} */
74 
75 /* {{{ proto \Couchbase\LookupInBuilder LookupInBuilder::exists(string $path, array $options = []) */
PHP_METHOD(LookupInBuilder,exists)76 PHP_METHOD(LookupInBuilder, exists)
77 {
78     pcbc_lookup_in_builder_t *obj;
79     const char *path = NULL;
80     pcbc_str_arg_size path_len = 0;
81     int rv;
82     zval *options = NULL;
83     pcbc_sd_spec_t *spec;
84 
85     obj = Z_LOOKUP_IN_BUILDER_OBJ_P(getThis());
86 
87     rv = zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|z", &path, &path_len, &options);
88     if (rv == FAILURE) {
89         RETURN_NULL();
90     }
91 
92     spec = ecalloc(1, sizeof(pcbc_sd_spec_t));
93     spec->next = NULL;
94     spec->s.sdcmd = LCB_SDCMD_EXISTS;
95     spec->s.options = pcbc_subdoc_options_to_flags(1, 1, options TSRMLS_CC);
96     PCBC_SDSPEC_COPY_PATH(spec, path, path_len);
97     if (obj->tail) {
98         obj->tail->next = spec;
99     }
100     obj->tail = spec;
101     if (obj->head == NULL) {
102         obj->head = obj->tail;
103     }
104     obj->nspecs++;
105 
106     RETURN_ZVAL(getThis(), 1, 0);
107 } /* }}} */
108 
109 /* {{{ proto \Couchbase\LookupInBuilder LookupInBuilder::getCount(string $path, array $options = []) */
PHP_METHOD(LookupInBuilder,getCount)110 PHP_METHOD(LookupInBuilder, getCount)
111 {
112     pcbc_lookup_in_builder_t *obj;
113     const char *path = NULL;
114     pcbc_str_arg_size path_len = 0;
115     int rv;
116     zval *options = NULL;
117     pcbc_sd_spec_t *spec;
118 
119     obj = Z_LOOKUP_IN_BUILDER_OBJ_P(getThis());
120 
121     rv = zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|z", &path, &path_len, &options);
122     if (rv == FAILURE) {
123         RETURN_NULL();
124     }
125 
126     spec = ecalloc(1, sizeof(pcbc_sd_spec_t));
127     spec->next = NULL;
128     spec->s.sdcmd = LCB_SDCMD_GET_COUNT;
129     spec->s.options = pcbc_subdoc_options_to_flags(1, 1, options TSRMLS_CC);
130     PCBC_SDSPEC_COPY_PATH(spec, path, path_len);
131     if (obj->tail) {
132         obj->tail->next = spec;
133     }
134     obj->tail = spec;
135     if (obj->head == NULL) {
136         obj->head = obj->tail;
137     }
138     obj->nspecs++;
139 
140     RETURN_ZVAL(getThis(), 1, 0);
141 } /* }}} */
142 
143 /* {{{ proto \Couchbase\LookupInBuilder LookupInBuilder::execute() */
PHP_METHOD(LookupInBuilder,execute)144 PHP_METHOD(LookupInBuilder, execute)
145 {
146     pcbc_lookup_in_builder_t *obj;
147     int rv;
148 
149     obj = Z_LOOKUP_IN_BUILDER_OBJ_P(getThis());
150 
151     rv = zend_parse_parameters_none();
152     if (rv == FAILURE) {
153         RETURN_NULL();
154     }
155 
156     pcbc_bucket_subdoc_request(obj->bucket, obj, 1, return_value TSRMLS_CC);
157 } /* }}} */
158 
159 ZEND_BEGIN_ARG_INFO_EX(ai_LookupInBuilder_none, 0, 0, 0)
160 ZEND_END_ARG_INFO()
161 
162 ZEND_BEGIN_ARG_INFO_EX(ai_LookupInBuilder_get, 0, 0, 1)
163 ZEND_ARG_INFO(0, path)
164 ZEND_ARG_INFO(0, options)
165 ZEND_END_ARG_INFO()
166 
167 // clang-format off
168 zend_function_entry lookup_in_builder_methods[] = {
169     PHP_ME(LookupInBuilder, __construct, ai_LookupInBuilder_none, ZEND_ACC_PRIVATE | ZEND_ACC_FINAL | ZEND_ACC_CTOR)
170     PHP_ME(LookupInBuilder, get, ai_LookupInBuilder_get, ZEND_ACC_PUBLIC)
171     PHP_ME(LookupInBuilder, getCount, ai_LookupInBuilder_get, ZEND_ACC_PUBLIC)
172     PHP_ME(LookupInBuilder, exists, ai_LookupInBuilder_get, ZEND_ACC_PUBLIC)
173     PHP_ME(LookupInBuilder, execute, ai_LookupInBuilder_none, ZEND_ACC_PUBLIC)
174     PHP_FE_END
175 };
176 // clang-format on
177 
178 zend_object_handlers pcbc_lookup_in_builder_handlers;
179 
pcbc_lookup_in_builder_init(zval * return_value,zval * bucket,const char * id,int id_len,zval * args,int num_args TSRMLS_DC)180 void pcbc_lookup_in_builder_init(zval *return_value, zval *bucket, const char *id, int id_len,
181 #if PHP_VERSION_ID >= 70000
182                                  zval *args,
183 #else
184                                  zval ***args,
185 #endif
186                                  int num_args TSRMLS_DC)
187 {
188     pcbc_lookup_in_builder_t *builder;
189 
190     object_init_ex(return_value, pcbc_lookup_in_builder_ce);
191     builder = Z_LOOKUP_IN_BUILDER_OBJ_P(return_value);
192 #if PHP_VERSION_ID >= 70000
193     ZVAL_COPY(&builder->bucket_zval, bucket);
194 #else
195     Z_ADDREF_P(bucket);
196     builder->bucket_zval = bucket;
197 #endif
198     builder->bucket = Z_BUCKET_OBJ_P(bucket);
199     builder->id_len = id_len;
200     builder->id = estrndup(id, id_len);
201     builder->nspecs = 0;
202     builder->head = NULL;
203     builder->tail = NULL;
204     if (num_args && args) {
205         int i;
206         for (i = 0; i < num_args; ++i) {
207             PCBC_ZVAL *path;
208 #if PHP_VERSION_ID >= 70000
209             path = &args[i];
210 #else
211             path = args[i];
212 #endif
213             if (PCBC_P(*path) && Z_TYPE_P(PCBC_P(*path)) != IS_STRING) {
214                 pcbc_log(LOGARGS(builder, WARN), "path has to be a string (skipping argument #%d)", i);
215                 continue;
216             }
217             pcbc_lookup_in_builder_get(builder, Z_STRVAL_P(PCBC_P(*path)), Z_STRLEN_P(PCBC_P(*path)), NULL TSRMLS_CC);
218         }
219     }
220 }
221 
lookup_in_builder_free_object(pcbc_free_object_arg * object TSRMLS_DC)222 static void lookup_in_builder_free_object(pcbc_free_object_arg *object TSRMLS_DC) /* {{{ */
223 {
224     pcbc_lookup_in_builder_t *obj = Z_LOOKUP_IN_BUILDER_OBJ(object);
225     pcbc_sd_spec_t *spec;
226 
227     if (obj->id != NULL) {
228         efree(obj->id);
229     }
230     spec = obj->head;
231     while (spec) {
232         pcbc_sd_spec_t *tmp = spec;
233         spec = spec->next;
234         PCBC_SDSPEC_FREE_PATH(tmp);
235         efree(tmp);
236     }
237     obj->head = obj->tail = NULL;
238     Z_DELREF_P(PCBC_P(obj->bucket_zval));
239     ZVAL_UNDEF(PCBC_P(obj->bucket_zval));
240     obj->bucket = NULL;
241     zend_object_std_dtor(&obj->std TSRMLS_CC);
242 #if PHP_VERSION_ID < 70000
243     efree(obj);
244 #endif
245 } /* }}} */
246 
lookup_in_builder_create_object(zend_class_entry * class_type TSRMLS_DC)247 static pcbc_create_object_retval lookup_in_builder_create_object(zend_class_entry *class_type TSRMLS_DC)
248 {
249     pcbc_lookup_in_builder_t *obj = NULL;
250 
251     obj = PCBC_ALLOC_OBJECT_T(pcbc_lookup_in_builder_t, class_type);
252 
253     zend_object_std_init(&obj->std, class_type TSRMLS_CC);
254     object_properties_init(&obj->std, class_type);
255 
256 #if PHP_VERSION_ID >= 70000
257     obj->std.handlers = &pcbc_lookup_in_builder_handlers;
258     return &obj->std;
259 #else
260     {
261         zend_object_value ret;
262         ret.handle = zend_objects_store_put(obj, (zend_objects_store_dtor_t)zend_objects_destroy_object,
263                                             lookup_in_builder_free_object, NULL TSRMLS_CC);
264         ret.handlers = &pcbc_lookup_in_builder_handlers;
265         return ret;
266     }
267 #endif
268 }
269 
lookup_in_builder_get_debug_info(zval * object,int * is_temp TSRMLS_DC)270 static HashTable *lookup_in_builder_get_debug_info(zval *object, int *is_temp TSRMLS_DC) /* {{{ */
271 {
272     pcbc_lookup_in_builder_t *obj = NULL;
273 #if PHP_VERSION_ID >= 70000
274     zval retval;
275 #else
276     zval retval = zval_used_for_init;
277 #endif
278     PCBC_ZVAL specs;
279     pcbc_sd_spec_t *spec;
280 
281     *is_temp = 1;
282     obj = Z_LOOKUP_IN_BUILDER_OBJ_P(object);
283 
284     array_init(&retval);
285     ADD_ASSOC_STRING(&retval, "id", obj->id);
286 
287     PCBC_ZVAL_ALLOC(specs);
288     array_init_size(PCBC_P(specs), obj->nspecs);
289 
290     spec = obj->head;
291     while (spec) {
292         PCBC_ZVAL s;
293         char *path = NULL;
294         int path_len = 0;
295 #if PHP_VERSION_ID < 70000
296         MAKE_STD_ZVAL(s);
297 #endif
298         array_init(PCBC_P(s));
299         switch (spec->s.sdcmd) {
300         case LCB_SDCMD_GET:
301             ADD_ASSOC_STRING(PCBC_P(s), "cmd", "get");
302             PCBC_SDSPEC_GET_PATH(spec, path, path_len);
303             ADD_ASSOC_STRINGL(PCBC_P(s), "path", path, path_len);
304             ADD_ASSOC_LONG_EX(PCBC_P(s), "options", spec->s.options);
305             break;
306         case LCB_SDCMD_EXISTS:
307             ADD_ASSOC_STRING(PCBC_P(s), "cmd", "exists");
308             PCBC_SDSPEC_GET_PATH(spec, path, path_len);
309             ADD_ASSOC_STRINGL(PCBC_P(s), "path", path, path_len);
310             ADD_ASSOC_LONG_EX(PCBC_P(s), "options", spec->s.options);
311             break;
312         }
313         add_next_index_zval(PCBC_P(specs), PCBC_P(s));
314         spec = spec->next;
315     }
316     add_assoc_zval(&retval, "specs", PCBC_P(specs));
317 
318     return Z_ARRVAL(retval);
319 } /* }}} */
320 
PHP_MINIT_FUNCTION(LookupInBuilder)321 PHP_MINIT_FUNCTION(LookupInBuilder)
322 {
323     zend_class_entry ce;
324 
325     INIT_NS_CLASS_ENTRY(ce, "Couchbase", "LookupInBuilder", lookup_in_builder_methods);
326     pcbc_lookup_in_builder_ce = zend_register_internal_class(&ce TSRMLS_CC);
327     pcbc_lookup_in_builder_ce->create_object = lookup_in_builder_create_object;
328     PCBC_CE_DISABLE_SERIALIZATION(pcbc_lookup_in_builder_ce);
329 
330     memcpy(&pcbc_lookup_in_builder_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
331     pcbc_lookup_in_builder_handlers.get_debug_info = lookup_in_builder_get_debug_info;
332 #if PHP_VERSION_ID >= 70000
333     pcbc_lookup_in_builder_handlers.free_obj = lookup_in_builder_free_object;
334     pcbc_lookup_in_builder_handlers.offset = XtOffsetOf(pcbc_lookup_in_builder_t, std);
335 #endif
336 
337     zend_register_class_alias("\\CouchbaseLookupInBuilder", pcbc_lookup_in_builder_ce);
338     return SUCCESS;
339 }
340