1 /**
2  * Copyright 2015-2017 DataStax, 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 "php_driver.h"
18 #include "php_driver_types.h"
19 #include "util/collections.h"
20 #include "util/hash.h"
21 #include "util/types.h"
22 #include "src/Collection.h"
23 
24 zend_class_entry *php_driver_collection_ce = NULL;
25 
26 void
php_driver_collection_add(php_driver_collection * collection,zval * object TSRMLS_DC)27 php_driver_collection_add(php_driver_collection *collection, zval *object TSRMLS_DC)
28 {
29   PHP5TO7_ZEND_HASH_NEXT_INDEX_INSERT(&collection->values, object, sizeof(zval *));
30   Z_TRY_ADDREF_P(object);
31   collection->dirty = 1;
32 }
33 
34 static int
php_driver_collection_del(php_driver_collection * collection,ulong index)35 php_driver_collection_del(php_driver_collection *collection, ulong index)
36 {
37   if (zend_hash_index_del(&collection->values, index) == SUCCESS) {
38     collection->dirty = 1;
39     return 1;
40   }
41 
42   return 0;
43 }
44 
45 static int
php_driver_collection_get(php_driver_collection * collection,ulong index,php5to7_zval * zvalue)46 php_driver_collection_get(php_driver_collection *collection, ulong index, php5to7_zval *zvalue)
47 {
48   php5to7_zval *value;
49   if (PHP5TO7_ZEND_HASH_INDEX_FIND(&collection->values, index, value)) {
50     *zvalue = *value;
51     return 1;
52   }
53   return 0;
54 }
55 
56 static int
php_driver_collection_find(php_driver_collection * collection,zval * object,long * index TSRMLS_DC)57 php_driver_collection_find(php_driver_collection *collection, zval *object, long *index TSRMLS_DC)
58 {
59   php5to7_ulong num_key;
60   php5to7_zval *current;
61   PHP5TO7_ZEND_HASH_FOREACH_NUM_KEY_VAL(&collection->values, num_key, current) {
62     zval compare;
63     is_equal_function(&compare, object, PHP5TO7_ZVAL_MAYBE_DEREF(current) TSRMLS_CC);
64     if (PHP5TO7_ZVAL_IS_TRUE_P(&compare)) {
65       *index = (long) num_key;
66       return 1;
67     }
68   } PHP5TO7_ZEND_HASH_FOREACH_END(&collection->values);
69 
70   return 0;
71 }
72 
73 static void
php_driver_collection_populate(php_driver_collection * collection,zval * array)74 php_driver_collection_populate(php_driver_collection *collection, zval *array)
75 {
76   php5to7_zval *current;
77   PHP5TO7_ZEND_HASH_FOREACH_VAL(&collection->values, current) {
78     if (add_next_index_zval(array, PHP5TO7_ZVAL_MAYBE_DEREF(current)) == SUCCESS)
79       Z_TRY_ADDREF_P(PHP5TO7_ZVAL_MAYBE_DEREF(current));
80     else
81       break;
82   } PHP5TO7_ZEND_HASH_FOREACH_END(&collection->values);
83 }
84 
85 /* {{{ Collection::__construct(type) */
PHP_METHOD(Collection,__construct)86 PHP_METHOD(Collection, __construct)
87 {
88   php_driver_collection *self;
89   zval *type;
90 
91   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &type) == FAILURE)
92     return;
93 
94   self = PHP_DRIVER_GET_COLLECTION(getThis());
95 
96   if (Z_TYPE_P(type) == IS_STRING) {
97     CassValueType value_type;
98     if (!php_driver_value_type(Z_STRVAL_P(type), &value_type TSRMLS_CC))
99       return;
100     self->type = php_driver_type_collection_from_value_type(value_type TSRMLS_CC);
101   } else if (Z_TYPE_P(type) == IS_OBJECT &&
102              instanceof_function(Z_OBJCE_P(type), php_driver_type_ce TSRMLS_CC)) {
103     if (!php_driver_type_validate(type, "type" TSRMLS_CC)) {
104       return;
105     }
106     self->type = php_driver_type_collection(type TSRMLS_CC);
107     Z_ADDREF_P(type);
108   } else {
109     INVALID_ARGUMENT(type, "a string or an instance of " PHP_DRIVER_NAMESPACE "\\Type");
110   }
111 }
112 /* }}} */
113 
114 /* {{{ Collection::type() */
PHP_METHOD(Collection,type)115 PHP_METHOD(Collection, type)
116 {
117   php_driver_collection *self = PHP_DRIVER_GET_COLLECTION(getThis());
118   RETURN_ZVAL(PHP5TO7_ZVAL_MAYBE_P(self->type), 1, 0);
119 }
120 
121 /* {{{ Collection::values() */
PHP_METHOD(Collection,values)122 PHP_METHOD(Collection, values)
123 {
124   php_driver_collection *collection = NULL;
125   array_init(return_value);
126   collection = PHP_DRIVER_GET_COLLECTION(getThis());
127   php_driver_collection_populate(collection, return_value);
128 }
129 /* }}} */
130 
131 /* {{{ Collection::add(mixed) */
PHP_METHOD(Collection,add)132 PHP_METHOD(Collection, add)
133 {
134   php_driver_collection *self = NULL;
135   php5to7_zval_args args = NULL;
136   int argc = 0, i;
137   php_driver_type *type;
138 
139   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "+", &args, &argc) == FAILURE)
140     return;
141 
142   self = PHP_DRIVER_GET_COLLECTION(getThis());
143   type = PHP_DRIVER_GET_TYPE(PHP5TO7_ZVAL_MAYBE_P(self->type));
144 
145   for (i = 0; i < argc; i++) {
146     if (Z_TYPE_P(PHP5TO7_ZVAL_ARG(args[i])) == IS_NULL) {
147       PHP5TO7_MAYBE_EFREE(args);
148       zend_throw_exception_ex(php_driver_invalid_argument_exception_ce, 0 TSRMLS_CC,
149                               "Invalid value: null is not supported inside collections");
150       RETURN_FALSE;
151     }
152 
153     if (!php_driver_validate_object(PHP5TO7_ZVAL_ARG(args[i]),
154                                     PHP5TO7_ZVAL_MAYBE_P(type->data.collection.value_type) TSRMLS_CC)) {
155       PHP5TO7_MAYBE_EFREE(args);
156       RETURN_FALSE;
157     }
158   }
159 
160   for (i = 0; i < argc; i++) {
161     php_driver_collection_add(self, PHP5TO7_ZVAL_ARG(args[i]) TSRMLS_CC);
162   }
163 
164   PHP5TO7_MAYBE_EFREE(args);
165   RETVAL_LONG(zend_hash_num_elements(&self->values));
166 }
167 /* }}} */
168 
169 /* {{{ Collection::get(int) */
PHP_METHOD(Collection,get)170 PHP_METHOD(Collection, get)
171 {
172   long key;
173   php_driver_collection *self = NULL;
174   php5to7_zval value;
175 
176   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &key) == FAILURE)
177     return;
178 
179   self = PHP_DRIVER_GET_COLLECTION(getThis());
180 
181   if (php_driver_collection_get(self, (ulong) key, &value))
182     RETURN_ZVAL(PHP5TO7_ZVAL_MAYBE_P(value), 1, 0);
183 }
184 /* }}} */
185 
186 /* {{{ Collection::find(mixed) */
PHP_METHOD(Collection,find)187 PHP_METHOD(Collection, find)
188 {
189   zval *object;
190   php_driver_collection *collection = NULL;
191   long index;
192 
193   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &object) == FAILURE)
194     return;
195 
196   collection = PHP_DRIVER_GET_COLLECTION(getThis());
197 
198   if (php_driver_collection_find(collection, object, &index TSRMLS_CC))
199     RETURN_LONG(index);
200 }
201 /* }}} */
202 
203 /* {{{ Collection::count() */
PHP_METHOD(Collection,count)204 PHP_METHOD(Collection, count)
205 {
206   php_driver_collection *collection = PHP_DRIVER_GET_COLLECTION(getThis());
207   RETURN_LONG(zend_hash_num_elements(&collection->values));
208 }
209 /* }}} */
210 
211 /* {{{ Collection::current() */
PHP_METHOD(Collection,current)212 PHP_METHOD(Collection, current)
213 {
214   php5to7_zval *current;
215   php_driver_collection *collection = PHP_DRIVER_GET_COLLECTION(getThis());
216 
217   if (PHP5TO7_ZEND_HASH_GET_CURRENT_DATA(&collection->values, current)) {
218     RETURN_ZVAL(PHP5TO7_ZVAL_MAYBE_DEREF(current), 1, 0);
219   }
220 }
221 /* }}} */
222 
223 /* {{{ Collection::key() */
PHP_METHOD(Collection,key)224 PHP_METHOD(Collection, key)
225 {
226   php5to7_ulong num_key;
227   php_driver_collection *collection = PHP_DRIVER_GET_COLLECTION(getThis());
228   if (PHP5TO7_ZEND_HASH_GET_CURRENT_KEY(&collection->values, NULL, &num_key) == HASH_KEY_IS_LONG) {
229     RETURN_LONG(num_key);
230   }
231 }
232 /* }}} */
233 
234 /* {{{ Collection::next() */
PHP_METHOD(Collection,next)235 PHP_METHOD(Collection, next)
236 {
237   php_driver_collection *collection = PHP_DRIVER_GET_COLLECTION(getThis());
238   zend_hash_move_forward(&collection->values);
239 }
240 /* }}} */
241 
242 /* {{{ Collection::valid() */
PHP_METHOD(Collection,valid)243 PHP_METHOD(Collection, valid)
244 {
245   php_driver_collection *collection = PHP_DRIVER_GET_COLLECTION(getThis());
246   RETURN_BOOL(zend_hash_has_more_elements(&collection->values) == SUCCESS);
247 }
248 /* }}} */
249 
250 /* {{{ Collection::rewind() */
PHP_METHOD(Collection,rewind)251 PHP_METHOD(Collection, rewind)
252 {
253   php_driver_collection *collection = PHP_DRIVER_GET_COLLECTION(getThis());
254   zend_hash_internal_pointer_reset(&collection->values);
255 }
256 /* }}} */
257 
258 /* {{{ Collection::remove(key) */
PHP_METHOD(Collection,remove)259 PHP_METHOD(Collection, remove)
260 {
261   long index;
262   php_driver_collection *collection = NULL;
263 
264   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) == FAILURE) {
265     return;
266   }
267 
268   collection = PHP_DRIVER_GET_COLLECTION(getThis());
269 
270   if (php_driver_collection_del(collection, (ulong) index)) {
271     RETURN_TRUE;
272   }
273 
274   RETURN_FALSE;
275 }
276 /* }}} */
277 
278 ZEND_BEGIN_ARG_INFO_EX(arginfo__construct, 0, ZEND_RETURN_VALUE, 1)
279   ZEND_ARG_INFO(0, type)
280 ZEND_END_ARG_INFO()
281 
282 ZEND_BEGIN_ARG_INFO_EX(arginfo_value, 0, ZEND_RETURN_VALUE, 1)
283   ZEND_ARG_INFO(0, value)
284 ZEND_END_ARG_INFO()
285 
286 ZEND_BEGIN_ARG_INFO_EX(arginfo_index, 0, ZEND_RETURN_VALUE, 1)
287   ZEND_ARG_INFO(0, index)
288 ZEND_END_ARG_INFO()
289 
290 ZEND_BEGIN_ARG_INFO_EX(arginfo_none, 0, ZEND_RETURN_VALUE, 0)
291 ZEND_END_ARG_INFO()
292 
293 static zend_function_entry php_driver_collection_methods[] = {
294   PHP_ME(Collection, __construct, arginfo__construct, ZEND_ACC_CTOR|ZEND_ACC_PUBLIC)
295   PHP_ME(Collection, type, arginfo_none, ZEND_ACC_PUBLIC)
296   PHP_ME(Collection, values, arginfo_none, ZEND_ACC_PUBLIC)
297   PHP_ME(Collection, add, arginfo_value, ZEND_ACC_PUBLIC)
298   PHP_ME(Collection, get, arginfo_index, ZEND_ACC_PUBLIC)
299   PHP_ME(Collection, find, arginfo_value, ZEND_ACC_PUBLIC)
300   /* Countable */
301   PHP_ME(Collection, count, arginfo_none, ZEND_ACC_PUBLIC)
302   /* Iterator */
303   PHP_ME(Collection, current, arginfo_none, ZEND_ACC_PUBLIC)
304   PHP_ME(Collection, key, arginfo_none, ZEND_ACC_PUBLIC)
305   PHP_ME(Collection, next, arginfo_none, ZEND_ACC_PUBLIC)
306   PHP_ME(Collection, valid, arginfo_none, ZEND_ACC_PUBLIC)
307   PHP_ME(Collection, rewind, arginfo_none, ZEND_ACC_PUBLIC)
308   PHP_ME(Collection, remove, arginfo_index, ZEND_ACC_PUBLIC)
309   PHP_FE_END
310 };
311 
312 static php_driver_value_handlers php_driver_collection_handlers;
313 
314 static HashTable *
php_driver_collection_gc(zval * object,php5to7_zval_gc table,int * n TSRMLS_DC)315 php_driver_collection_gc(zval *object, php5to7_zval_gc table, int *n TSRMLS_DC)
316 {
317   *table = NULL;
318   *n = 0;
319   return zend_std_get_properties(object TSRMLS_CC);
320 }
321 
322 static HashTable *
php_driver_collection_properties(zval * object TSRMLS_DC)323 php_driver_collection_properties(zval *object TSRMLS_DC)
324 {
325   php5to7_zval values;
326 
327   php_driver_collection  *self = PHP_DRIVER_GET_COLLECTION(object);
328   HashTable             *props = zend_std_get_properties(object TSRMLS_CC);
329 
330   PHP5TO7_ZEND_HASH_UPDATE(props,
331                            "type", sizeof("type"),
332                            PHP5TO7_ZVAL_MAYBE_P(self->type), sizeof(zval));
333   Z_ADDREF_P(PHP5TO7_ZVAL_MAYBE_P(self->type));
334 
335   PHP5TO7_ZVAL_MAYBE_MAKE(values);
336   array_init(PHP5TO7_ZVAL_MAYBE_P(values));
337   php_driver_collection_populate(self, PHP5TO7_ZVAL_MAYBE_P(values));
338   PHP5TO7_ZEND_HASH_UPDATE(props, "values", sizeof("values"), PHP5TO7_ZVAL_MAYBE_P(values), sizeof(zval));
339 
340   return props;
341 }
342 
343 static int
php_driver_collection_compare(zval * obj1,zval * obj2 TSRMLS_DC)344 php_driver_collection_compare(zval *obj1, zval *obj2 TSRMLS_DC)
345 {
346   HashPosition pos1;
347   HashPosition pos2;
348   php5to7_zval *current1;
349   php5to7_zval *current2;
350   php_driver_collection *collection1;
351   php_driver_collection *collection2;
352   php_driver_type *type1;
353   php_driver_type *type2;
354   int result;
355 
356   if (Z_OBJCE_P(obj1) != Z_OBJCE_P(obj2))
357     return 1; /* different classes */
358 
359   collection1 = PHP_DRIVER_GET_COLLECTION(obj1);
360   collection2 = PHP_DRIVER_GET_COLLECTION(obj2);
361 
362   type1 = PHP_DRIVER_GET_TYPE(PHP5TO7_ZVAL_MAYBE_P(collection1->type));
363   type2 = PHP_DRIVER_GET_TYPE(PHP5TO7_ZVAL_MAYBE_P(collection2->type));
364 
365   result = php_driver_type_compare(type1, type2 TSRMLS_CC);
366   if (result != 0) return result;
367 
368   if (zend_hash_num_elements(&collection1->values) != zend_hash_num_elements(&collection2->values)) {
369     return zend_hash_num_elements(&collection1->values) < zend_hash_num_elements(&collection2->values) ? -1 : 1;
370   }
371 
372   zend_hash_internal_pointer_reset_ex(&collection1->values, &pos1);
373   zend_hash_internal_pointer_reset_ex(&collection2->values, &pos2);
374 
375   while (PHP5TO7_ZEND_HASH_GET_CURRENT_DATA_EX(&collection1->values, current1, &pos1) &&
376          PHP5TO7_ZEND_HASH_GET_CURRENT_DATA_EX(&collection2->values, current2, &pos2)) {
377     result = php_driver_value_compare(PHP5TO7_ZVAL_MAYBE_DEREF(current1),
378                                          PHP5TO7_ZVAL_MAYBE_DEREF(current2) TSRMLS_CC);
379     if (result != 0) return result;
380     zend_hash_move_forward_ex(&collection1->values, &pos1);
381     zend_hash_move_forward_ex(&collection2->values, &pos2);
382   }
383 
384   return 0;
385 }
386 
387 static unsigned
php_driver_collection_hash_value(zval * obj TSRMLS_DC)388 php_driver_collection_hash_value(zval *obj TSRMLS_DC)
389 {
390   php5to7_zval *current;
391   unsigned hashv = 0;
392   php_driver_collection *self = PHP_DRIVER_GET_COLLECTION(obj);
393 
394   if (!self->dirty) return self->hashv;
395 
396   PHP5TO7_ZEND_HASH_FOREACH_VAL(&self->values, current) {
397     hashv = php_driver_combine_hash(hashv,
398                                        php_driver_value_hash(PHP5TO7_ZVAL_MAYBE_DEREF(current) TSRMLS_CC));
399   } PHP5TO7_ZEND_HASH_FOREACH_END(&self->values);
400 
401   self->hashv = hashv;
402   self->dirty = 0;
403 
404   return hashv;
405 }
406 
407 static void
php_driver_collection_free(php5to7_zend_object_free * object TSRMLS_DC)408 php_driver_collection_free(php5to7_zend_object_free *object TSRMLS_DC)
409 {
410   php_driver_collection *self =
411       PHP5TO7_ZEND_OBJECT_GET(collection, object);
412 
413   zend_hash_destroy(&self->values);
414   PHP5TO7_ZVAL_MAYBE_DESTROY(self->type);
415 
416   zend_object_std_dtor(&self->zval TSRMLS_CC);
417   PHP5TO7_MAYBE_EFREE(self);
418 }
419 
420 static php5to7_zend_object
php_driver_collection_new(zend_class_entry * ce TSRMLS_DC)421 php_driver_collection_new(zend_class_entry *ce TSRMLS_DC)
422 {
423   php_driver_collection *self =
424       PHP5TO7_ZEND_OBJECT_ECALLOC(collection, ce);
425 
426   zend_hash_init(&self->values, 0, NULL, ZVAL_PTR_DTOR, 0);
427   self->dirty = 1;
428   PHP5TO7_ZVAL_UNDEF(self->type);
429 
430   PHP5TO7_ZEND_OBJECT_INIT(collection, self, ce);
431 }
432 
php_driver_define_Collection(TSRMLS_D)433 void php_driver_define_Collection(TSRMLS_D)
434 {
435   zend_class_entry ce;
436 
437   INIT_CLASS_ENTRY(ce, PHP_DRIVER_NAMESPACE "\\Collection", php_driver_collection_methods);
438   php_driver_collection_ce = zend_register_internal_class(&ce TSRMLS_CC);
439   zend_class_implements(php_driver_collection_ce TSRMLS_CC, 1, php_driver_value_ce);
440   memcpy(&php_driver_collection_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
441   php_driver_collection_handlers.std.get_properties  = php_driver_collection_properties;
442 #if PHP_VERSION_ID >= 50400
443   php_driver_collection_handlers.std.get_gc          = php_driver_collection_gc;
444 #endif
445   php_driver_collection_handlers.std.compare_objects = php_driver_collection_compare;
446   php_driver_collection_ce->ce_flags |= PHP5TO7_ZEND_ACC_FINAL;
447   php_driver_collection_ce->create_object = php_driver_collection_new;
448   zend_class_implements(php_driver_collection_ce TSRMLS_CC, 2, spl_ce_Countable, zend_ce_iterator);
449 
450   php_driver_collection_handlers.hash_value = php_driver_collection_hash_value;
451   php_driver_collection_handlers.std.clone_obj = NULL;
452 }
453