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(lvl) LCB_LOG_##lvl, NULL, "pcbc/mutation_state", __FILE__, __LINE__
21 
22 zend_class_entry *pcbc_mutation_state_ce;
23 
24 /* {{{ proto void MutationState::__construct() Should not be called directly */
PHP_METHOD(MutationState,__construct)25 PHP_METHOD(MutationState, __construct)
26 {
27     throw_pcbc_exception("Accessing private constructor.", LCB_EINVAL);
28 }
29 /* }}} */
30 
pcbc_add_token(pcbc_mutation_state_t * state,pcbc_mutation_token_t * token TSRMLS_DC)31 static void pcbc_add_token(pcbc_mutation_state_t *state, pcbc_mutation_token_t *token TSRMLS_DC)
32 {
33     pcbc_mutation_token_t *new_token = NULL, *t;
34 
35     t = state->head;
36     while (t) {
37         if (PCBC_MUTATION_TOKEN_VB(t) == PCBC_MUTATION_TOKEN_VB(t) && strcmp(t->bucket, token->bucket) == 0) {
38             if (PCBC_MUTATION_TOKEN_SEQ(t) < PCBC_MUTATION_TOKEN_SEQ(token)) {
39                 new_token = t;
40             }
41             if (PCBC_MUTATION_TOKEN_SEQ(t) == PCBC_MUTATION_TOKEN_SEQ(token) &&
42                 PCBC_MUTATION_TOKEN_ID(t) == PCBC_MUTATION_TOKEN_ID(token)) {
43                 return;
44             }
45         }
46         t = t->next;
47     }
48     if (new_token == NULL) {
49         new_token = ecalloc(1, sizeof(pcbc_mutation_token_t));
50         new_token->next = NULL;
51         new_token->bucket = estrdup(token->bucket);
52         if (state->tail) {
53             state->tail->next = new_token;
54         }
55         state->tail = new_token;
56         if (state->head == NULL) {
57             state->head = state->tail;
58         }
59         state->ntokens++;
60     }
61     PCBC_MUTATION_TOKEN_ID(new_token) = PCBC_MUTATION_TOKEN_ID(token);
62     PCBC_MUTATION_TOKEN_SEQ(new_token) = PCBC_MUTATION_TOKEN_SEQ(token);
63     PCBC_MUTATION_TOKEN_VB(new_token) = PCBC_MUTATION_TOKEN_VB(token);
64 }
65 
66 #define ADD_TOKEN_FROM_ZVAL(source)                                                                                    \
67     if (instanceof_function(Z_OBJCE_P(source), pcbc_mutation_token_ce TSRMLS_CC)) {                                    \
68         pcbc_add_token(state, Z_MUTATION_TOKEN_OBJ_P(source) TSRMLS_CC);                                               \
69     } else if (instanceof_function(Z_OBJCE_P(source), pcbc_document_ce TSRMLS_CC)) {                                   \
70         zval *val;                                                                                                     \
71         PCBC_READ_PROPERTY(val, pcbc_document_ce, source, "token", 0);                                                 \
72         if (val && Z_TYPE_P(val) == IS_OBJECT &&                                                                       \
73             instanceof_function(Z_OBJCE_P(val), pcbc_mutation_token_ce TSRMLS_CC)) {                                   \
74             pcbc_add_token(state, Z_MUTATION_TOKEN_OBJ_P(val) TSRMLS_CC);                                              \
75         }                                                                                                              \
76     } else if (instanceof_function(Z_OBJCE_P(source), pcbc_document_fragment_ce TSRMLS_CC)) {                          \
77         zval *val;                                                                                                     \
78         PCBC_READ_PROPERTY(val, pcbc_document_fragment_ce, source, "token", 0);                                        \
79         if (val && Z_TYPE_P(val) == IS_OBJECT &&                                                                       \
80             instanceof_function(Z_OBJCE_P(val), pcbc_mutation_token_ce TSRMLS_CC)) {                                   \
81             pcbc_add_token(state, Z_MUTATION_TOKEN_OBJ_P(val) TSRMLS_CC);                                              \
82         }                                                                                                              \
83     } else {                                                                                                           \
84         throw_pcbc_exception("Object with mutation token expected (Document, DocumentFragment or MutationToken)",      \
85                              LCB_EINVAL);                                                                              \
86     }
87 
88 /* {{{ proto \Couchbase\MutationState MutationState::from(array $source = []) */
PHP_METHOD(MutationState,from)89 PHP_METHOD(MutationState, from)
90 {
91     zval *source = NULL;
92     pcbc_mutation_state_t *state;
93     int rv;
94 
95     rv = zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &source);
96     if (rv == FAILURE) {
97         RETURN_NULL();
98     }
99 
100     pcbc_mutation_state_init(return_value, source TSRMLS_CC);
101     state = Z_MUTATION_STATE_OBJ_P(return_value);
102 
103     switch (Z_TYPE_P(source)) {
104     case IS_OBJECT:
105         ADD_TOKEN_FROM_ZVAL(source);
106         break;
107     case IS_ARRAY: {
108 #if PHP_VERSION_ID >= 70000
109         zval *entry;
110 
111         ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(source), entry)
112         {
113             ADD_TOKEN_FROM_ZVAL(entry);
114         }
115         ZEND_HASH_FOREACH_END();
116 #else
117         HashPosition pos;
118         zval **entry;
119 
120         zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(source), &pos);
121         while (zend_hash_get_current_data_ex(Z_ARRVAL_P(source), (void **)&entry, &pos) == SUCCESS) {
122             ADD_TOKEN_FROM_ZVAL(*entry);
123             zend_hash_move_forward_ex(Z_ARRVAL_P(source), &pos);
124         }
125 #endif
126     } break;
127     default:
128         throw_pcbc_exception(
129             "Array or object with mutation state expected (Document, DocumentFragment or MutationToken)", LCB_EINVAL);
130     }
131 } /* }}} */
132 
133 /* {{{ proto \Couchbase\MutationState MutationState::add(array $source) */
PHP_METHOD(MutationState,add)134 PHP_METHOD(MutationState, add)
135 {
136     pcbc_mutation_state_t *state;
137     zval *source;
138     int rv;
139 
140     rv = zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &source);
141     if (rv == FAILURE) {
142         RETURN_NULL();
143     }
144     state = Z_MUTATION_STATE_OBJ_P(getThis());
145 
146     switch (Z_TYPE_P(source)) {
147     case IS_OBJECT:
148         ADD_TOKEN_FROM_ZVAL(source);
149         break;
150     case IS_ARRAY: {
151 #if PHP_VERSION_ID >= 70000
152         zval *entry;
153 
154         ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(source), entry)
155         {
156             ADD_TOKEN_FROM_ZVAL(entry);
157         }
158         ZEND_HASH_FOREACH_END();
159 #else
160         HashPosition pos;
161         zval **entry;
162 
163         zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(source), &pos);
164         while (zend_hash_get_current_data_ex(Z_ARRVAL_P(source), (void **)&entry, &pos) == SUCCESS) {
165             ADD_TOKEN_FROM_ZVAL(*entry);
166             zend_hash_move_forward_ex(Z_ARRVAL_P(source), &pos);
167         }
168 #endif
169     } break;
170     default:
171         throw_pcbc_exception(
172             "Array or object with mutation state expected (Document, DocumentFragment or MutationToken)", LCB_EINVAL);
173     }
174 
175     RETURN_ZVAL(getThis(), 1, 0);
176 } /* }}} */
177 
178 #undef ADD_TOKEN_FROM_ZVAL
179 
pcbc_mutation_state_export_for_n1ql(pcbc_mutation_state_t * obj,zval * scan_vectors TSRMLS_DC)180 void pcbc_mutation_state_export_for_n1ql(pcbc_mutation_state_t *obj, zval *scan_vectors TSRMLS_DC)
181 {
182     pcbc_mutation_token_t *token;
183     array_init(scan_vectors);
184     token = obj->head;
185     while (token) {
186         PCBC_ZVAL new_group;
187         zval *bucket_group;
188         bucket_group = php_array_fetch(scan_vectors, token->bucket);
189         if (!bucket_group) {
190             PCBC_ZVAL_ALLOC(new_group);
191             array_init(PCBC_P(new_group));
192 #if PHP_VERSION_ID >= 70000
193             add_assoc_zval_ex(scan_vectors, token->bucket, strlen(token->bucket), PCBC_P(new_group));
194 #else
195             add_assoc_zval_ex(scan_vectors, token->bucket, strlen(token->bucket) + 1, PCBC_P(new_group));
196 #endif
197             bucket_group = PCBC_P(new_group);
198         }
199         {
200             PCBC_ZVAL pair;
201             char buf[22] = {0};
202 
203             PCBC_ZVAL_ALLOC(pair);
204             array_init_size(PCBC_P(pair), 2);
205             add_next_index_long(PCBC_P(pair), PCBC_MUTATION_TOKEN_SEQ(token));
206             snprintf(buf, 21, "%llu", (unsigned long long)PCBC_MUTATION_TOKEN_ID(token));
207             ADD_NEXT_INDEX_STRING(PCBC_P(pair), buf);
208             snprintf(buf, 21, "%d", (int)PCBC_MUTATION_TOKEN_VB(token));
209 #if PHP_VERSION_ID >= 70000
210             zend_hash_str_update(Z_ARRVAL_P(bucket_group), buf, strlen(buf), PCBC_P(pair) TSRMLS_CC);
211 #else
212             zend_hash_update(Z_ARRVAL_P(bucket_group), buf, strlen(buf) + 1, &pair, sizeof(pair), NULL);
213 #endif
214         }
215         token = token->next;
216     }
217 }
218 
pcbc_mutation_state_export_for_search(pcbc_mutation_state_t * obj,zval * scan_vectors TSRMLS_DC)219 void pcbc_mutation_state_export_for_search(pcbc_mutation_state_t *obj, zval *scan_vectors TSRMLS_DC)
220 {
221     pcbc_mutation_token_t *token;
222     array_init(scan_vectors);
223     token = obj->head;
224     while (token) {
225         char *token_key = NULL;
226         int token_key_len;
227 
228         token_key_len = spprintf(&token_key, 0, "%d/%llu", PCBC_MUTATION_TOKEN_VB(token),
229                                  (unsigned long long)PCBC_MUTATION_TOKEN_ID(token));
230         add_assoc_long_ex(scan_vectors, token_key, token_key_len + 1, PCBC_MUTATION_TOKEN_SEQ(token));
231         efree(token_key);
232         token = token->next;
233     }
234 }
235 
236 ZEND_BEGIN_ARG_INFO_EX(ai_MutationState_none, 0, 0, 0)
237 ZEND_END_ARG_INFO()
238 
239 ZEND_BEGIN_ARG_INFO_EX(ai_MutationState_fromString, 0, 0, 1)
240 ZEND_ARG_INFO(0, statement)
241 ZEND_END_ARG_INFO()
242 
243 ZEND_BEGIN_ARG_INFO_EX(ai_MutationState_add, 0, 0, 1)
244 ZEND_ARG_INFO(0, source)
245 ZEND_END_ARG_INFO()
246 
247 // clang-format off
248 zend_function_entry mutation_state_methods[] = {
249     PHP_ME(MutationState, __construct, ai_MutationState_none, ZEND_ACC_PRIVATE | ZEND_ACC_FINAL | ZEND_ACC_CTOR)
250     PHP_ME(MutationState, from, ai_MutationState_add, ZEND_ACC_STATIC | ZEND_ACC_PUBLIC)
251     PHP_ME(MutationState, add, ai_MutationState_add, ZEND_ACC_PUBLIC)
252     PHP_FE_END
253 };
254 // clang-format on
255 
256 zend_object_handlers pcbc_mutation_state_handlers;
257 
pcbc_mutation_state_init(zval * return_value,zval * source TSRMLS_DC)258 void pcbc_mutation_state_init(zval *return_value, zval *source TSRMLS_DC)
259 {
260     pcbc_mutation_state_t *state;
261 
262     object_init_ex(return_value, pcbc_mutation_state_ce);
263     state = Z_MUTATION_STATE_OBJ_P(return_value);
264     state->head = NULL;
265     state->tail = NULL;
266     state->ntokens = 0;
267 }
268 
mutation_state_free_object(pcbc_free_object_arg * object TSRMLS_DC)269 static void mutation_state_free_object(pcbc_free_object_arg *object TSRMLS_DC) /* {{{ */
270 {
271     pcbc_mutation_state_t *obj = Z_MUTATION_STATE_OBJ(object);
272     pcbc_mutation_token_t *token;
273 
274     token = obj->head;
275     while (token) {
276         pcbc_mutation_token_t *tmp = token;
277         token = token->next;
278         efree(tmp->bucket);
279         efree(tmp);
280     }
281     obj->head = obj->tail = NULL;
282 
283     zend_object_std_dtor(&obj->std TSRMLS_CC);
284 #if PHP_VERSION_ID < 70000
285     efree(obj);
286 #endif
287 } /* }}} */
288 
mutation_state_create_object(zend_class_entry * class_type TSRMLS_DC)289 static pcbc_create_object_retval mutation_state_create_object(zend_class_entry *class_type TSRMLS_DC)
290 {
291     pcbc_mutation_state_t *obj = NULL;
292 
293     obj = PCBC_ALLOC_OBJECT_T(pcbc_mutation_state_t, class_type);
294 
295     zend_object_std_init(&obj->std, class_type TSRMLS_CC);
296     object_properties_init(&obj->std, class_type);
297 
298 #if PHP_VERSION_ID >= 70000
299     obj->std.handlers = &pcbc_mutation_state_handlers;
300     return &obj->std;
301 #else
302     {
303         zend_object_value ret;
304         ret.handle = zend_objects_store_put(obj, (zend_objects_store_dtor_t)zend_objects_destroy_object,
305                                             mutation_state_free_object, NULL TSRMLS_CC);
306         ret.handlers = &pcbc_mutation_state_handlers;
307         return ret;
308     }
309 #endif
310 }
311 
mutation_state_get_debug_info(zval * object,int * is_temp TSRMLS_DC)312 static HashTable *mutation_state_get_debug_info(zval *object, int *is_temp TSRMLS_DC) /* {{{ */
313 {
314     pcbc_mutation_state_t *obj = NULL;
315 #if PHP_VERSION_ID >= 70000
316     zval retval;
317 #else
318     zval retval = zval_used_for_init;
319 #endif
320     pcbc_mutation_token_t *token;
321 
322     *is_temp = 1;
323     obj = Z_MUTATION_STATE_OBJ_P(object);
324 
325     array_init_size(&retval, obj->ntokens);
326     token = obj->head;
327     while (token) {
328         PCBC_ZVAL t;
329         char *num36;
330 
331         PCBC_ZVAL_ALLOC(t);
332         array_init_size(PCBC_P(t), 4);
333 
334         ADD_ASSOC_STRING(PCBC_P(t), "bucket", token->bucket);
335         ADD_ASSOC_LONG_EX(PCBC_P(t), "vbucketId", PCBC_MUTATION_TOKEN_VB(token));
336         num36 = pcbc_base36_encode_str(PCBC_MUTATION_TOKEN_ID(token));
337         ADD_ASSOC_STRING(PCBC_P(t), "vbucketUuid", num36);
338         efree(num36);
339         num36 = pcbc_base36_encode_str(PCBC_MUTATION_TOKEN_SEQ(token));
340         ADD_ASSOC_STRING(PCBC_P(t), "sequenceNumber", num36);
341         efree(num36);
342         add_next_index_zval(&retval, PCBC_P(t));
343         token = token->next;
344     }
345     return Z_ARRVAL(retval);
346 } /* }}} */
347 
PHP_MINIT_FUNCTION(MutationState)348 PHP_MINIT_FUNCTION(MutationState)
349 {
350     zend_class_entry ce;
351 
352     INIT_NS_CLASS_ENTRY(ce, "Couchbase", "MutationState", mutation_state_methods);
353     pcbc_mutation_state_ce = zend_register_internal_class(&ce TSRMLS_CC);
354     pcbc_mutation_state_ce->create_object = mutation_state_create_object;
355     PCBC_CE_DISABLE_SERIALIZATION(pcbc_mutation_state_ce);
356 
357     memcpy(&pcbc_mutation_state_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
358     pcbc_mutation_state_handlers.get_debug_info = mutation_state_get_debug_info;
359 #if PHP_VERSION_ID >= 70000
360     pcbc_mutation_state_handlers.free_obj = mutation_state_free_object;
361     pcbc_mutation_state_handlers.offset = XtOffsetOf(pcbc_mutation_state_t, std);
362 #endif
363 
364     zend_register_class_alias("\\CouchbaseMutationState", pcbc_mutation_state_ce);
365     return SUCCESS;
366 }
367