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