1 /*
2   +----------------------------------------------------------------------+
3   | Copyright (c) 1997-2009 The PHP Group                                |
4   +----------------------------------------------------------------------+
5   | This source file is subject to version 3.01 of the PHP license,      |
6   | that is bundled with this package in the file LICENSE, and is        |
7   | available through the world-wide-web at the following url:           |
8   | http://www.php.net/license/3_01.txt                                  |
9   | If you did not receive a copy of the PHP license and are unable to   |
10   | obtain it through the world-wide-web, please send a note to          |
11   | license@php.net so we can mail you a copy immediately.               |
12   +----------------------------------------------------------------------+
13   | Author: Michael Grunder <michael.grunder@gmail.com>                  |
14   | Maintainer: Nicolas Favre-Felix <n.favre-felix@owlient.eu>           |
15   +----------------------------------------------------------------------+
16 */
17 
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21 
22 #include "common.h"
23 #include "php_redis.h"
24 #include "ext/standard/info.h"
25 #include "crc16.h"
26 #include "redis_cluster.h"
27 #include "redis_commands.h"
28 #include <zend_exceptions.h>
29 #include "library.h"
30 #include <php_variables.h>
31 #include <SAPI.h>
32 
33 zend_class_entry *redis_cluster_ce;
34 
35 /* Exception handler */
36 zend_class_entry *redis_cluster_exception_ce;
37 
38 /* Handlers for RedisCluster */
39 zend_object_handlers RedisCluster_handlers;
40 
41 ZEND_BEGIN_ARG_INFO_EX(arginfo_ctor, 0, 0, 1)
42     ZEND_ARG_INFO(0, name)
43     ZEND_ARG_ARRAY_INFO(0, seeds, 0)
44     ZEND_ARG_INFO(0, timeout)
45     ZEND_ARG_INFO(0, read_timeout)
46     ZEND_ARG_INFO(0, persistent)
47     ZEND_ARG_INFO(0, auth)
48 ZEND_END_ARG_INFO()
49 
50 ZEND_BEGIN_ARG_INFO_EX(arginfo_del, 0, 0, 1)
51     ZEND_ARG_INFO(0, key)
52     ZEND_ARG_VARIADIC_INFO(0, other_keys)
53 ZEND_END_ARG_INFO()
54 
55 ZEND_BEGIN_ARG_INFO_EX(arginfo_mget, 0, 0, 1)
56     ZEND_ARG_ARRAY_INFO(0, keys, 0)
57 ZEND_END_ARG_INFO()
58 
59 ZEND_BEGIN_ARG_INFO_EX(arginfo_keys, 0, 0, 1)
60     ZEND_ARG_INFO(0, pattern)
61 ZEND_END_ARG_INFO()
62 
63 ZEND_BEGIN_ARG_INFO_EX(arginfo_key_or_address, 0, 0, 1)
64     ZEND_ARG_INFO(0, key_or_address)
65 ZEND_END_ARG_INFO()
66 
67 ZEND_BEGIN_ARG_INFO_EX(arginfo_key_or_address_variadic, 0, 0, 1)
68     ZEND_ARG_INFO(0, key_or_address)
69     ZEND_ARG_INFO(0, arg)
70     ZEND_ARG_VARIADIC_INFO(0, other_args)
71 ZEND_END_ARG_INFO()
72 
73 ZEND_BEGIN_ARG_INFO_EX(arginfo_info, 0, 0, 1)
74     ZEND_ARG_INFO(0, key_or_address)
75     ZEND_ARG_INFO(0, option)
76 ZEND_END_ARG_INFO()
77 
78 ZEND_BEGIN_ARG_INFO_EX(arginfo_flush, 0, 0, 1)
79     ZEND_ARG_INFO(0, key_or_address)
80     ZEND_ARG_INFO(0, async)
81 ZEND_END_ARG_INFO()
82 
83 /* Argument info for HSCAN, SSCAN, HSCAN */
84 ZEND_BEGIN_ARG_INFO_EX(arginfo_kscan_cl, 0, 0, 2)
85     ZEND_ARG_INFO(0, str_key)
86     ZEND_ARG_INFO(1, i_iterator)
87     ZEND_ARG_INFO(0, str_pattern)
88     ZEND_ARG_INFO(0, i_count)
89 ZEND_END_ARG_INFO()
90 
91 /* Argument info for SCAN */
92 ZEND_BEGIN_ARG_INFO_EX(arginfo_scan_cl, 0, 0, 2)
93     ZEND_ARG_INFO(1, i_iterator)
94     ZEND_ARG_INFO(0, str_node)
95     ZEND_ARG_INFO(0, str_pattern)
96     ZEND_ARG_INFO(0, i_count)
97 ZEND_END_ARG_INFO()
98 
99 ZEND_BEGIN_ARG_INFO_EX(arginfo_acl_cl, 0, 0, 2)
100     ZEND_ARG_INFO(0, key_or_address)
101     ZEND_ARG_INFO(0, subcmd)
102     ZEND_ARG_VARIADIC_INFO(0, args)
103 ZEND_END_ARG_INFO()
104 
105 /* Function table */
106 zend_function_entry redis_cluster_functions[] = {
107     PHP_ME(RedisCluster, __construct, arginfo_ctor, ZEND_ACC_PUBLIC)
108     PHP_ME(RedisCluster, _masters, arginfo_void, ZEND_ACC_PUBLIC)
109     PHP_ME(RedisCluster, _prefix, arginfo_key, ZEND_ACC_PUBLIC)
110     PHP_ME(RedisCluster, _redir, arginfo_void, ZEND_ACC_PUBLIC)
111     PHP_ME(RedisCluster, _serialize, arginfo_value, ZEND_ACC_PUBLIC)
112     PHP_ME(RedisCluster, _unserialize, arginfo_value, ZEND_ACC_PUBLIC)
113     PHP_ME(RedisCluster, _compress, arginfo_value, ZEND_ACC_PUBLIC)
114     PHP_ME(RedisCluster, _uncompress, arginfo_value, ZEND_ACC_PUBLIC)
115     PHP_ME(RedisCluster, _pack, arginfo_value, ZEND_ACC_PUBLIC)
116     PHP_ME(RedisCluster, _unpack, arginfo_value, ZEND_ACC_PUBLIC)
117     PHP_ME(RedisCluster, acl, arginfo_acl_cl, ZEND_ACC_PUBLIC)
118     PHP_ME(RedisCluster, append, arginfo_key_value, ZEND_ACC_PUBLIC)
119     PHP_ME(RedisCluster, bgrewriteaof, arginfo_key_or_address, ZEND_ACC_PUBLIC)
120     PHP_ME(RedisCluster, bgsave, arginfo_key_or_address, ZEND_ACC_PUBLIC)
121     PHP_ME(RedisCluster, bitcount, arginfo_key, ZEND_ACC_PUBLIC)
122     PHP_ME(RedisCluster, bitop, arginfo_bitop, ZEND_ACC_PUBLIC)
123     PHP_ME(RedisCluster, bitpos, arginfo_bitpos, ZEND_ACC_PUBLIC)
124     PHP_ME(RedisCluster, blpop, arginfo_blrpop, ZEND_ACC_PUBLIC)
125     PHP_ME(RedisCluster, brpop, arginfo_blrpop, ZEND_ACC_PUBLIC)
126     PHP_ME(RedisCluster, brpoplpush, arginfo_brpoplpush, ZEND_ACC_PUBLIC)
127     PHP_ME(RedisCluster, clearlasterror, arginfo_void, ZEND_ACC_PUBLIC)
128     PHP_ME(RedisCluster, bzpopmax, arginfo_blrpop, ZEND_ACC_PUBLIC)
129     PHP_ME(RedisCluster, bzpopmin, arginfo_blrpop, ZEND_ACC_PUBLIC)
130     PHP_ME(RedisCluster, client, arginfo_key_or_address_variadic, ZEND_ACC_PUBLIC)
131     PHP_ME(RedisCluster, close, arginfo_void, ZEND_ACC_PUBLIC)
132     PHP_ME(RedisCluster, cluster, arginfo_key_or_address_variadic, ZEND_ACC_PUBLIC)
133     PHP_ME(RedisCluster, command, arginfo_command, ZEND_ACC_PUBLIC)
134     PHP_ME(RedisCluster, config, arginfo_key_or_address_variadic, ZEND_ACC_PUBLIC)
135     PHP_ME(RedisCluster, dbsize, arginfo_key_or_address, ZEND_ACC_PUBLIC)
136     PHP_ME(RedisCluster, decr, arginfo_key, ZEND_ACC_PUBLIC)
137     PHP_ME(RedisCluster, decrby, arginfo_key_value, ZEND_ACC_PUBLIC)
138     PHP_ME(RedisCluster, del, arginfo_del, ZEND_ACC_PUBLIC)
139     PHP_ME(RedisCluster, discard, arginfo_void, ZEND_ACC_PUBLIC)
140     PHP_ME(RedisCluster, dump, arginfo_key, ZEND_ACC_PUBLIC)
141     PHP_ME(RedisCluster, echo, arginfo_echo, ZEND_ACC_PUBLIC)
142     PHP_ME(RedisCluster, eval, arginfo_eval, ZEND_ACC_PUBLIC)
143     PHP_ME(RedisCluster, evalsha, arginfo_evalsha, ZEND_ACC_PUBLIC)
144     PHP_ME(RedisCluster, exec, arginfo_void, ZEND_ACC_PUBLIC)
145     PHP_ME(RedisCluster, exists, arginfo_key, ZEND_ACC_PUBLIC)
146     PHP_ME(RedisCluster, expire, arginfo_expire, ZEND_ACC_PUBLIC)
147     PHP_ME(RedisCluster, expireat, arginfo_key_timestamp, ZEND_ACC_PUBLIC)
148     PHP_ME(RedisCluster, flushall, arginfo_flush, ZEND_ACC_PUBLIC)
149     PHP_ME(RedisCluster, flushdb, arginfo_flush, ZEND_ACC_PUBLIC)
150     PHP_ME(RedisCluster, geoadd, arginfo_geoadd, ZEND_ACC_PUBLIC)
151     PHP_ME(RedisCluster, geodist, arginfo_geodist, ZEND_ACC_PUBLIC)
152     PHP_ME(RedisCluster, geohash, arginfo_key_members, ZEND_ACC_PUBLIC)
153     PHP_ME(RedisCluster, geopos, arginfo_key_members, ZEND_ACC_PUBLIC)
154     PHP_ME(RedisCluster, georadius, arginfo_georadius, ZEND_ACC_PUBLIC)
155     PHP_ME(RedisCluster, georadius_ro, arginfo_georadius, ZEND_ACC_PUBLIC)
156     PHP_ME(RedisCluster, georadiusbymember, arginfo_georadiusbymember, ZEND_ACC_PUBLIC)
157     PHP_ME(RedisCluster, georadiusbymember_ro, arginfo_georadiusbymember, ZEND_ACC_PUBLIC)
158     PHP_ME(RedisCluster, get, arginfo_key, ZEND_ACC_PUBLIC)
159     PHP_ME(RedisCluster, getbit, arginfo_key_offset, ZEND_ACC_PUBLIC)
160     PHP_ME(RedisCluster, getlasterror, arginfo_void, ZEND_ACC_PUBLIC)
161     PHP_ME(RedisCluster, getmode, arginfo_void, ZEND_ACC_PUBLIC)
162     PHP_ME(RedisCluster, getoption, arginfo_getoption, ZEND_ACC_PUBLIC)
163     PHP_ME(RedisCluster, getrange, arginfo_key_start_end, ZEND_ACC_PUBLIC)
164     PHP_ME(RedisCluster, getset, arginfo_key_value, ZEND_ACC_PUBLIC)
165     PHP_ME(RedisCluster, hdel, arginfo_key_members, ZEND_ACC_PUBLIC)
166     PHP_ME(RedisCluster, hexists, arginfo_key_member, ZEND_ACC_PUBLIC)
167     PHP_ME(RedisCluster, hget, arginfo_key_member, ZEND_ACC_PUBLIC)
168     PHP_ME(RedisCluster, hgetall, arginfo_key, ZEND_ACC_PUBLIC)
169     PHP_ME(RedisCluster, hincrby, arginfo_key_member_value, ZEND_ACC_PUBLIC)
170     PHP_ME(RedisCluster, hincrbyfloat, arginfo_key_member_value, ZEND_ACC_PUBLIC)
171     PHP_ME(RedisCluster, hkeys, arginfo_key, ZEND_ACC_PUBLIC)
172     PHP_ME(RedisCluster, hlen, arginfo_key, ZEND_ACC_PUBLIC)
173     PHP_ME(RedisCluster, hmget, arginfo_hmget, ZEND_ACC_PUBLIC)
174     PHP_ME(RedisCluster, hmset, arginfo_hmset, ZEND_ACC_PUBLIC)
175     PHP_ME(RedisCluster, hscan, arginfo_kscan_cl, ZEND_ACC_PUBLIC)
176     PHP_ME(RedisCluster, hset, arginfo_key_member_value, ZEND_ACC_PUBLIC)
177     PHP_ME(RedisCluster, hsetnx, arginfo_key_member_value, ZEND_ACC_PUBLIC)
178     PHP_ME(RedisCluster, hstrlen, arginfo_key_member, ZEND_ACC_PUBLIC)
179     PHP_ME(RedisCluster, hvals, arginfo_key, ZEND_ACC_PUBLIC)
180     PHP_ME(RedisCluster, incr, arginfo_key, ZEND_ACC_PUBLIC)
181     PHP_ME(RedisCluster, incrby, arginfo_key_value, ZEND_ACC_PUBLIC)
182     PHP_ME(RedisCluster, incrbyfloat, arginfo_key_value, ZEND_ACC_PUBLIC)
183     PHP_ME(RedisCluster, info, arginfo_info, ZEND_ACC_PUBLIC)
184     PHP_ME(RedisCluster, keys, arginfo_keys, ZEND_ACC_PUBLIC)
185     PHP_ME(RedisCluster, lastsave, arginfo_key_or_address, ZEND_ACC_PUBLIC)
186     PHP_ME(RedisCluster, lget, arginfo_lindex, ZEND_ACC_PUBLIC)
187     PHP_ME(RedisCluster, lindex, arginfo_lindex, ZEND_ACC_PUBLIC)
188     PHP_ME(RedisCluster, linsert, arginfo_linsert, ZEND_ACC_PUBLIC)
189     PHP_ME(RedisCluster, llen, arginfo_key, ZEND_ACC_PUBLIC)
190     PHP_ME(RedisCluster, lpop, arginfo_key, ZEND_ACC_PUBLIC)
191     PHP_ME(RedisCluster, lpush, arginfo_key_value, ZEND_ACC_PUBLIC)
192     PHP_ME(RedisCluster, lpushx, arginfo_key_value, ZEND_ACC_PUBLIC)
193     PHP_ME(RedisCluster, lrange, arginfo_key_start_end, ZEND_ACC_PUBLIC)
194     PHP_ME(RedisCluster, lrem, arginfo_key_value, ZEND_ACC_PUBLIC)
195     PHP_ME(RedisCluster, lset, arginfo_lset, ZEND_ACC_PUBLIC)
196     PHP_ME(RedisCluster, ltrim, arginfo_ltrim, ZEND_ACC_PUBLIC)
197     PHP_ME(RedisCluster, mget, arginfo_mget, ZEND_ACC_PUBLIC)
198     PHP_ME(RedisCluster, mset, arginfo_pairs, ZEND_ACC_PUBLIC)
199     PHP_ME(RedisCluster, msetnx, arginfo_pairs, ZEND_ACC_PUBLIC)
200     PHP_ME(RedisCluster, multi, arginfo_void, ZEND_ACC_PUBLIC)
201     PHP_ME(RedisCluster, object, arginfo_object, ZEND_ACC_PUBLIC)
202     PHP_ME(RedisCluster, persist, arginfo_key, ZEND_ACC_PUBLIC)
203     PHP_ME(RedisCluster, pexpire, arginfo_key_timestamp, ZEND_ACC_PUBLIC)
204     PHP_ME(RedisCluster, pexpireat, arginfo_key_timestamp, ZEND_ACC_PUBLIC)
205     PHP_ME(RedisCluster, pfadd, arginfo_pfadd, ZEND_ACC_PUBLIC)
206     PHP_ME(RedisCluster, pfcount, arginfo_key, ZEND_ACC_PUBLIC)
207     PHP_ME(RedisCluster, pfmerge, arginfo_pfmerge, ZEND_ACC_PUBLIC)
208     PHP_ME(RedisCluster, ping, arginfo_key_or_address, ZEND_ACC_PUBLIC)
209     PHP_ME(RedisCluster, psetex, arginfo_key_expire_value, ZEND_ACC_PUBLIC)
210     PHP_ME(RedisCluster, psubscribe, arginfo_psubscribe, ZEND_ACC_PUBLIC)
211     PHP_ME(RedisCluster, pttl, arginfo_key, ZEND_ACC_PUBLIC)
212     PHP_ME(RedisCluster, publish, arginfo_publish, ZEND_ACC_PUBLIC)
213     PHP_ME(RedisCluster, pubsub, arginfo_key_or_address_variadic, ZEND_ACC_PUBLIC)
214     PHP_ME(RedisCluster, punsubscribe, arginfo_punsubscribe, ZEND_ACC_PUBLIC)
215     PHP_ME(RedisCluster, randomkey, arginfo_key_or_address, ZEND_ACC_PUBLIC)
216     PHP_ME(RedisCluster, rawcommand, arginfo_rawcommand, ZEND_ACC_PUBLIC)
217     PHP_ME(RedisCluster, rename, arginfo_key_newkey, ZEND_ACC_PUBLIC)
218     PHP_ME(RedisCluster, renamenx, arginfo_key_newkey, ZEND_ACC_PUBLIC)
219     PHP_ME(RedisCluster, restore, arginfo_restore, ZEND_ACC_PUBLIC)
220     PHP_ME(RedisCluster, role, arginfo_void, ZEND_ACC_PUBLIC)
221     PHP_ME(RedisCluster, rpop, arginfo_key, ZEND_ACC_PUBLIC)
222     PHP_ME(RedisCluster, rpoplpush, arginfo_rpoplpush, ZEND_ACC_PUBLIC)
223     PHP_ME(RedisCluster, rpush, arginfo_key_value, ZEND_ACC_PUBLIC)
224     PHP_ME(RedisCluster, rpushx, arginfo_key_value, ZEND_ACC_PUBLIC)
225     PHP_ME(RedisCluster, sadd, arginfo_key_value, ZEND_ACC_PUBLIC)
226     PHP_ME(RedisCluster, saddarray, arginfo_sadd_array, ZEND_ACC_PUBLIC)
227     PHP_ME(RedisCluster, save, arginfo_key_or_address, ZEND_ACC_PUBLIC)
228     PHP_ME(RedisCluster, scan, arginfo_scan_cl, ZEND_ACC_PUBLIC)
229     PHP_ME(RedisCluster, scard, arginfo_key, ZEND_ACC_PUBLIC)
230     PHP_ME(RedisCluster, script, arginfo_key_or_address_variadic, ZEND_ACC_PUBLIC)
231     PHP_ME(RedisCluster, sdiff, arginfo_nkeys, ZEND_ACC_PUBLIC)
232     PHP_ME(RedisCluster, sdiffstore, arginfo_dst_nkeys, ZEND_ACC_PUBLIC)
233     PHP_ME(RedisCluster, set, arginfo_set, ZEND_ACC_PUBLIC)
234     PHP_ME(RedisCluster, setbit, arginfo_key_offset_value, ZEND_ACC_PUBLIC)
235     PHP_ME(RedisCluster, setex, arginfo_key_expire_value, ZEND_ACC_PUBLIC)
236     PHP_ME(RedisCluster, setnx, arginfo_key_value, ZEND_ACC_PUBLIC)
237     PHP_ME(RedisCluster, setoption, arginfo_setoption, ZEND_ACC_PUBLIC)
238     PHP_ME(RedisCluster, setrange, arginfo_key_offset_value, ZEND_ACC_PUBLIC)
239     PHP_ME(RedisCluster, sinter, arginfo_nkeys, ZEND_ACC_PUBLIC)
240     PHP_ME(RedisCluster, sinterstore, arginfo_dst_nkeys, ZEND_ACC_PUBLIC)
241     PHP_ME(RedisCluster, sismember, arginfo_key_value, ZEND_ACC_PUBLIC)
242     PHP_ME(RedisCluster, slowlog, arginfo_key_or_address_variadic, ZEND_ACC_PUBLIC)
243     PHP_ME(RedisCluster, smembers, arginfo_key, ZEND_ACC_PUBLIC)
244     PHP_ME(RedisCluster, smove, arginfo_smove, ZEND_ACC_PUBLIC)
245     PHP_ME(RedisCluster, sort, arginfo_sort, ZEND_ACC_PUBLIC)
246     PHP_ME(RedisCluster, spop, arginfo_key, ZEND_ACC_PUBLIC)
247     PHP_ME(RedisCluster, srandmember, arginfo_srand_member, ZEND_ACC_PUBLIC)
248     PHP_ME(RedisCluster, srem, arginfo_key_value, ZEND_ACC_PUBLIC)
249     PHP_ME(RedisCluster, sscan, arginfo_kscan_cl, ZEND_ACC_PUBLIC)
250     PHP_ME(RedisCluster, strlen, arginfo_key, ZEND_ACC_PUBLIC)
251     PHP_ME(RedisCluster, subscribe, arginfo_subscribe, ZEND_ACC_PUBLIC)
252     PHP_ME(RedisCluster, sunion, arginfo_nkeys, ZEND_ACC_PUBLIC)
253     PHP_ME(RedisCluster, sunionstore, arginfo_dst_nkeys, ZEND_ACC_PUBLIC)
254     PHP_ME(RedisCluster, time, arginfo_void, ZEND_ACC_PUBLIC)
255     PHP_ME(RedisCluster, ttl, arginfo_key, ZEND_ACC_PUBLIC)
256     PHP_ME(RedisCluster, type, arginfo_key, ZEND_ACC_PUBLIC)
257     PHP_ME(RedisCluster, unsubscribe, arginfo_unsubscribe, ZEND_ACC_PUBLIC)
258     PHP_ME(RedisCluster, unlink, arginfo_del, ZEND_ACC_PUBLIC)
259     PHP_ME(RedisCluster, unwatch, arginfo_void, ZEND_ACC_PUBLIC)
260     PHP_ME(RedisCluster, watch, arginfo_watch, ZEND_ACC_PUBLIC)
261     PHP_ME(RedisCluster, xack, arginfo_xack, ZEND_ACC_PUBLIC)
262     PHP_ME(RedisCluster, xadd, arginfo_xadd, ZEND_ACC_PUBLIC)
263     PHP_ME(RedisCluster, xclaim, arginfo_xclaim, ZEND_ACC_PUBLIC)
264     PHP_ME(RedisCluster, xdel, arginfo_xdel, ZEND_ACC_PUBLIC)
265     PHP_ME(RedisCluster, xgroup, arginfo_xgroup, ZEND_ACC_PUBLIC)
266     PHP_ME(RedisCluster, xinfo, arginfo_xinfo, ZEND_ACC_PUBLIC)
267     PHP_ME(RedisCluster, xlen, arginfo_key, ZEND_ACC_PUBLIC)
268     PHP_ME(RedisCluster, xpending, arginfo_xpending, ZEND_ACC_PUBLIC)
269     PHP_ME(RedisCluster, xrange, arginfo_xrange, ZEND_ACC_PUBLIC)
270     PHP_ME(RedisCluster, xread, arginfo_xread, ZEND_ACC_PUBLIC)
271     PHP_ME(RedisCluster, xreadgroup, arginfo_xreadgroup, ZEND_ACC_PUBLIC)
272     PHP_ME(RedisCluster, xrevrange, arginfo_xrange, ZEND_ACC_PUBLIC)
273     PHP_ME(RedisCluster, xtrim, arginfo_xtrim, ZEND_ACC_PUBLIC)
274     PHP_ME(RedisCluster, zadd, arginfo_zadd, ZEND_ACC_PUBLIC)
275     PHP_ME(RedisCluster, zcard, arginfo_key, ZEND_ACC_PUBLIC)
276     PHP_ME(RedisCluster, zcount, arginfo_key_min_max, ZEND_ACC_PUBLIC)
277     PHP_ME(RedisCluster, zincrby, arginfo_zincrby, ZEND_ACC_PUBLIC)
278     PHP_ME(RedisCluster, zinterstore, arginfo_zstore, ZEND_ACC_PUBLIC)
279     PHP_ME(RedisCluster, zlexcount, arginfo_key_min_max, ZEND_ACC_PUBLIC)
280     PHP_ME(RedisCluster, zpopmax, arginfo_key, ZEND_ACC_PUBLIC)
281     PHP_ME(RedisCluster, zpopmin, arginfo_key, ZEND_ACC_PUBLIC)
282     PHP_ME(RedisCluster, zrange, arginfo_zrange, ZEND_ACC_PUBLIC)
283     PHP_ME(RedisCluster, zrangebylex, arginfo_zrangebylex, ZEND_ACC_PUBLIC)
284     PHP_ME(RedisCluster, zrangebyscore, arginfo_zrangebyscore, ZEND_ACC_PUBLIC)
285     PHP_ME(RedisCluster, zrank, arginfo_key_member, ZEND_ACC_PUBLIC)
286     PHP_ME(RedisCluster, zrem, arginfo_key_members, ZEND_ACC_PUBLIC)
287     PHP_ME(RedisCluster, zremrangebylex, arginfo_key_min_max, ZEND_ACC_PUBLIC)
288     PHP_ME(RedisCluster, zremrangebyrank, arginfo_key_min_max, ZEND_ACC_PUBLIC)
289     PHP_ME(RedisCluster, zremrangebyscore, arginfo_key_min_max, ZEND_ACC_PUBLIC)
290     PHP_ME(RedisCluster, zrevrange, arginfo_zrange, ZEND_ACC_PUBLIC)
291     PHP_ME(RedisCluster, zrevrangebylex, arginfo_zrangebylex, ZEND_ACC_PUBLIC)
292     PHP_ME(RedisCluster, zrevrangebyscore, arginfo_zrangebyscore, ZEND_ACC_PUBLIC)
293     PHP_ME(RedisCluster, zrevrank, arginfo_key_member, ZEND_ACC_PUBLIC)
294     PHP_ME(RedisCluster, zscan, arginfo_kscan_cl, ZEND_ACC_PUBLIC)
295     PHP_ME(RedisCluster, zscore, arginfo_key_member, ZEND_ACC_PUBLIC)
296     PHP_ME(RedisCluster, zunionstore, arginfo_zstore, ZEND_ACC_PUBLIC)
297     PHP_FE_END
298 };
299 
300 /* Our context seeds will be a hash table with RedisSock* pointers */
ht_free_seed(zval * data)301 static void ht_free_seed(zval *data) {
302     RedisSock *redis_sock = *(RedisSock**)data;
303     if (redis_sock) redis_free_socket(redis_sock);
304 }
305 
306 /* Free redisClusterNode objects we've stored */
ht_free_node(zval * data)307 static void ht_free_node(zval *data) {
308     redisClusterNode *node = *(redisClusterNode**)data;
309     cluster_free_node(node);
310 }
311 
312 /* Create redisCluster context */
create_cluster_context(zend_class_entry * class_type)313 zend_object * create_cluster_context(zend_class_entry *class_type) {
314     redisCluster *cluster;
315 
316     // Allocate our actual struct
317     cluster = ecalloc(1, sizeof(redisCluster) + zend_object_properties_size(class_type));
318 
319     // We're not currently subscribed anywhere
320     cluster->subscribed_slot = -1;
321 
322     // Allocate our RedisSock we'll use to store prefix/serialization flags
323     cluster->flags = ecalloc(1, sizeof(RedisSock));
324 
325     // Allocate our hash table for seeds
326     ALLOC_HASHTABLE(cluster->seeds);
327     zend_hash_init(cluster->seeds, 0, NULL, ht_free_seed, 0);
328 
329     // Allocate our hash table for connected Redis objects
330     ALLOC_HASHTABLE(cluster->nodes);
331     zend_hash_init(cluster->nodes, 0, NULL, ht_free_node, 0);
332 
333     // Initialize it
334     zend_object_std_init(&cluster->std, class_type);
335 
336     object_properties_init(&cluster->std, class_type);
337     memcpy(&RedisCluster_handlers, zend_get_std_object_handlers(), sizeof(RedisCluster_handlers));
338     RedisCluster_handlers.offset = XtOffsetOf(redisCluster, std);
339     RedisCluster_handlers.free_obj = free_cluster_context;
340     RedisCluster_handlers.clone_obj = NULL;
341 
342     cluster->std.handlers = &RedisCluster_handlers;
343 
344     return &cluster->std;
345 }
346 
347 /* Free redisCluster context */
free_cluster_context(zend_object * object)348 void free_cluster_context(zend_object *object) {
349     redisCluster *cluster = PHPREDIS_GET_OBJECT(redisCluster, object);
350 
351     cluster_free(cluster, 0);
352     zend_object_std_dtor(&cluster->std);
353 }
354 
355 /* Take user provided seeds and return unique and valid ones */
356 /* Attempt to connect to a Redis cluster provided seeds and timeout options */
redis_cluster_init(redisCluster * c,HashTable * ht_seeds,double timeout,double read_timeout,int persistent,zend_string * user,zend_string * pass,zval * context)357 static void redis_cluster_init(redisCluster *c, HashTable *ht_seeds, double timeout,
358                                double read_timeout, int persistent, zend_string *user,
359                                zend_string *pass, zval *context)
360 {
361     zend_string *hash = NULL, **seeds;
362     redisCachedCluster *cc;
363     uint32_t nseeds;
364     char *err;
365 
366     /* Validate our arguments and get a sanitized seed array */
367     seeds = cluster_validate_args(timeout, read_timeout, ht_seeds, &nseeds, &err);
368     if (seeds == NULL) {
369         CLUSTER_THROW_EXCEPTION(err, 0);
370         return;
371     }
372 
373     if (user && ZSTR_LEN(user))
374         c->flags->user = zend_string_copy(user);
375     if (pass && ZSTR_LEN(pass))
376         c->flags->pass = zend_string_copy(pass);
377     if (context) {
378         redis_sock_set_stream_context(c->flags, context);
379     }
380 
381     c->flags->timeout = timeout;
382     c->flags->read_timeout = read_timeout;
383     c->flags->persistent = persistent;
384     c->waitms = timeout * 1000L;
385 
386     /* Attempt to load slots from cache if caching is enabled */
387     if (CLUSTER_CACHING_ENABLED()) {
388         /* Exit early if we can load from cache */
389         hash = cluster_hash_seeds(seeds, nseeds);
390         if ((cc = cluster_cache_load(hash))) {
391             cluster_init_cache(c, cc);
392             goto cleanup;
393         }
394     }
395 
396     /* Initialize seeds and attempt to map keyspace */
397     cluster_init_seeds(c, seeds, nseeds);
398     if (cluster_map_keyspace(c) == SUCCESS && hash)
399         cluster_cache_store(hash, c->nodes);
400 
401 cleanup:
402     if (hash) zend_string_release(hash);
403     free_seed_array(seeds, nseeds);
404 }
405 
406 
407 /* Attempt to load a named cluster configured in php.ini */
redis_cluster_load(redisCluster * c,char * name,int name_len)408 void redis_cluster_load(redisCluster *c, char *name, int name_len) {
409     zval z_seeds, z_tmp, *z_value;
410     zend_string *user = NULL, *pass = NULL;
411     double timeout = 0, read_timeout = 0;
412     int persistent = 0;
413     char *iptr;
414     HashTable *ht_seeds = NULL;
415 
416     /* Seeds */
417     array_init(&z_seeds);
418     if ((iptr = INI_STR("redis.clusters.seeds")) != NULL) {
419         sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_seeds);
420     }
421     if ((z_value = zend_hash_str_find(Z_ARRVAL(z_seeds), name, name_len)) != NULL) {
422         ht_seeds = Z_ARRVAL_P(z_value);
423     } else {
424         zval_dtor(&z_seeds);
425         CLUSTER_THROW_EXCEPTION("Couldn't find seeds for cluster", 0);
426         return;
427     }
428 
429     /* Connection timeout */
430     if ((iptr = INI_STR("redis.clusters.timeout")) != NULL) {
431         array_init(&z_tmp);
432         sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
433         redis_conf_double(Z_ARRVAL(z_tmp), name, name_len, &timeout);
434         zval_dtor(&z_tmp);
435     }
436 
437     /* Read timeout */
438     if ((iptr = INI_STR("redis.clusters.read_timeout")) != NULL) {
439         array_init(&z_tmp);
440         sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
441         redis_conf_double(Z_ARRVAL(z_tmp), name, name_len, &read_timeout);
442         zval_dtor(&z_tmp);
443     }
444 
445     /* Persistent connections */
446     if ((iptr = INI_STR("redis.clusters.persistent")) != NULL) {
447         array_init(&z_tmp);
448         sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
449         redis_conf_bool(Z_ARRVAL(z_tmp), name, name_len, &persistent);
450         zval_dtor(&z_tmp);
451     }
452 
453     if ((iptr = INI_STR("redis.clusters.auth"))) {
454         array_init(&z_tmp);
455         sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_tmp);
456         redis_conf_auth(Z_ARRVAL(z_tmp), name, name_len, &user, &pass);
457         zval_dtor(&z_tmp);
458     }
459 
460     /* Attempt to create/connect to the cluster */
461     redis_cluster_init(c, ht_seeds, timeout, read_timeout, persistent, user, pass, NULL);
462 
463     /* Clean up */
464     zval_dtor(&z_seeds);
465     if (user) zend_string_release(user);
466     if (pass) zend_string_release(pass);
467 }
468 
469 /*
470  * PHP Methods
471  */
472 
473 /* Create a RedisCluster Object */
PHP_METHOD(RedisCluster,__construct)474 PHP_METHOD(RedisCluster, __construct) {
475     zval *object, *z_seeds = NULL, *z_auth = NULL, *context = NULL;
476     zend_string *user = NULL, *pass = NULL;
477     double timeout = 0.0, read_timeout = 0.0;
478     size_t name_len;
479     zend_bool persistent = 0;
480     redisCluster *c = GET_CONTEXT();
481     char *name;
482 
483     // Parse arguments
484     if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(),
485                                     "Os!|addbza!", &object, redis_cluster_ce, &name,
486                                     &name_len, &z_seeds, &timeout, &read_timeout,
487                                     &persistent, &z_auth, &context) == FAILURE)
488     {
489         RETURN_FALSE;
490     }
491 
492     /* If we've got a string try to load from INI */
493     if (ZEND_NUM_ARGS() < 2) {
494         if (name_len == 0) { // Require a name
495             CLUSTER_THROW_EXCEPTION("You must specify a name or pass seeds!", 0);
496         }
497         redis_cluster_load(c, name, name_len);
498         return;
499     }
500 
501     /* The normal case, loading from arguments */
502     redis_extract_auth_info(z_auth, &user, &pass);
503     redis_cluster_init(c, Z_ARRVAL_P(z_seeds), timeout, read_timeout,
504                        persistent, user, pass, context);
505 
506     if (user) zend_string_release(user);
507     if (pass) zend_string_release(pass);
508 }
509 
510 /*
511  * RedisCluster method implementation
512  */
513 
514 /* {{{ proto bool RedisCluster::close() */
PHP_METHOD(RedisCluster,close)515 PHP_METHOD(RedisCluster, close) {
516     cluster_disconnect(GET_CONTEXT(), 1);
517     RETURN_TRUE;
518 }
519 
520 /* {{{ proto string RedisCluster::get(string key) */
PHP_METHOD(RedisCluster,get)521 PHP_METHOD(RedisCluster, get) {
522     CLUSTER_PROCESS_KW_CMD("GET", redis_key_cmd, cluster_bulk_resp, 1);
523 }
524 /* }}} */
525 
526 /* {{{ proto bool RedisCluster::set(string key, string value) */
PHP_METHOD(RedisCluster,set)527 PHP_METHOD(RedisCluster, set) {
528     CLUSTER_PROCESS_CMD(set, cluster_bool_resp, 0);
529 }
530 /* }}} */
531 
532 /* Generic handler for MGET/MSET/MSETNX */
533 static int
distcmd_resp_handler(INTERNAL_FUNCTION_PARAMETERS,redisCluster * c,short slot,clusterMultiCmd * mc,zval * z_ret,int last,cluster_cb cb)534 distcmd_resp_handler(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, short slot,
535                      clusterMultiCmd *mc, zval *z_ret, int last, cluster_cb cb)
536 {
537     clusterMultiCtx *ctx;
538 
539     // Finalize multi command
540     cluster_multi_fini(mc);
541 
542     // Spin up multi context
543     ctx = emalloc(sizeof(clusterMultiCtx));
544     ctx->z_multi = z_ret;
545     ctx->count   = mc->argc;
546     ctx->last    = last;
547 
548     // Attempt to send the command
549     if (cluster_send_command(c,slot,mc->cmd.c,mc->cmd.len) < 0 || c->err != NULL) {
550         efree(ctx);
551         return -1;
552     }
553 
554     if (CLUSTER_IS_ATOMIC(c)) {
555         // Process response now
556         cb(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, (void*)ctx);
557     } else {
558         CLUSTER_ENQUEUE_RESPONSE(c, slot, cb, ctx);
559     }
560 
561     // Clear out our command but retain allocated memory
562     CLUSTER_MULTI_CLEAR(mc);
563 
564     return 0;
565 }
566 
567 /* Container struct for a key/value pair pulled from an array */
568 typedef struct clusterKeyValHT {
569     char kbuf[22];
570 
571     char  *key;
572     size_t key_len;
573     int key_free;
574     short slot;
575 
576     char *val;
577     size_t val_len;
578     int val_free;
579 } clusterKeyValHT;
580 
581 /* Helper to pull a key/value pair from a HashTable */
get_key_val_ht(redisCluster * c,HashTable * ht,HashPosition * ptr,clusterKeyValHT * kv)582 static int get_key_val_ht(redisCluster *c, HashTable *ht, HashPosition *ptr,
583                           clusterKeyValHT *kv)
584 {
585     zval *z_val;
586     zend_ulong idx;
587 
588     // Grab the key, convert it to a string using provided kbuf buffer if it's
589     // a LONG style key
590     zend_string *zkey;
591     switch (zend_hash_get_current_key_ex(ht, &zkey, &idx, ptr)) {
592         case HASH_KEY_IS_STRING:
593             kv->key_len = ZSTR_LEN(zkey);
594             kv->key = ZSTR_VAL(zkey);
595             break;
596         case HASH_KEY_IS_LONG:
597             kv->key_len = snprintf(kv->kbuf,sizeof(kv->kbuf),"%ld",(long)idx);
598             kv->key     = kv->kbuf;
599             break;
600         default:
601             CLUSTER_THROW_EXCEPTION("Internal Zend HashTable error", 0);
602             return -1;
603     }
604 
605     // Prefix our key if we need to, set the slot
606     kv->key_free = redis_key_prefix(c->flags, &(kv->key), &(kv->key_len));
607     kv->slot     = cluster_hash_key(kv->key, kv->key_len);
608 
609     // Now grab our value
610     if ((z_val = zend_hash_get_current_data_ex(ht, ptr)) == NULL) {
611         CLUSTER_THROW_EXCEPTION("Internal Zend HashTable error", 0);
612         return -1;
613     }
614 
615     // Serialize our value if required
616     kv->val_free = redis_pack(c->flags,z_val,&(kv->val),&(kv->val_len));
617 
618     // Success
619     return 0;
620 }
621 
622 /* Helper to pull, prefix, and hash a key from a HashTable value */
get_key_ht(redisCluster * c,HashTable * ht,HashPosition * ptr,clusterKeyValHT * kv)623 static int get_key_ht(redisCluster *c, HashTable *ht, HashPosition *ptr,
624                       clusterKeyValHT *kv)
625 {
626     zval *z_key;
627 
628     if ((z_key = zend_hash_get_current_data_ex(ht, ptr)) == NULL) {
629         // Shouldn't happen, but check anyway
630         CLUSTER_THROW_EXCEPTION("Internal Zend HashTable error", 0);
631         return -1;
632     }
633 
634     // Always want to work with strings
635     convert_to_string(z_key);
636 
637     kv->key = Z_STRVAL_P(z_key);
638     kv->key_len = Z_STRLEN_P(z_key);
639     kv->key_free = redis_key_prefix(c->flags, &(kv->key), &(kv->key_len));
640 
641     // Hash our key
642     kv->slot = cluster_hash_key(kv->key, kv->key_len);
643 
644     // Success
645     return 0;
646 }
647 
648 /* Turn variable arguments into a HashTable for processing */
method_args_to_ht(zval * z_args,int argc)649 static HashTable *method_args_to_ht(zval *z_args, int argc) {
650     HashTable *ht_ret;
651     int i;
652 
653     /* Allocate our hash table */
654     ALLOC_HASHTABLE(ht_ret);
655     zend_hash_init(ht_ret, argc, NULL, NULL, 0);
656 
657     /* Populate our return hash table with our arguments */
658     for (i = 0; i < argc; i++) {
659         zend_hash_next_index_insert(ht_ret, &z_args[i]);
660     }
661 
662     /* Return our hash table */
663     return ht_ret;
664 }
665 
666 /* Convenience handler for commands that take multiple keys such as
667  * MGET, DEL, and UNLINK */
cluster_mkey_cmd(INTERNAL_FUNCTION_PARAMETERS,char * kw,int kw_len,zval * z_ret,cluster_cb cb)668 static int cluster_mkey_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len,
669                             zval *z_ret, cluster_cb cb)
670 {
671     redisCluster *c = GET_CONTEXT();
672     clusterMultiCmd mc = {0};
673     clusterKeyValHT kv;
674     zval *z_args;
675     HashTable *ht_arr;
676     HashPosition ptr;
677     int i = 1, argc = ZEND_NUM_ARGS(), ht_free = 0;
678     short slot;
679 
680     /* If we don't have any arguments we're invalid */
681     if (!argc) return -1;
682 
683     /* Extract our arguments into an array */
684     z_args = ecalloc(argc, sizeof(zval));
685     if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
686         efree(z_args);
687         return -1;
688     }
689 
690     /* Determine if we're working with a single array or variadic args */
691     if (argc == 1 && Z_TYPE(z_args[0]) == IS_ARRAY) {
692         ht_arr = Z_ARRVAL(z_args[0]);
693         argc = zend_hash_num_elements(ht_arr);
694         if (!argc) {
695             efree(z_args);
696             return -1;
697         }
698     } else {
699         ht_arr = method_args_to_ht(z_args, argc);
700         ht_free = 1;
701     }
702 
703     /* MGET is readonly, DEL is not */
704     c->readonly = kw_len == 4 && CLUSTER_IS_ATOMIC(c);
705 
706     // Initialize our "multi" command handler with command/len
707     CLUSTER_MULTI_INIT(mc, kw, kw_len);
708 
709     // Process the first key outside of our loop, so we don't have to check if
710     // it's the first iteration every time, needlessly
711     zend_hash_internal_pointer_reset_ex(ht_arr, &ptr);
712     if (get_key_ht(c, ht_arr, &ptr, &kv) < 0) {
713         efree(z_args);
714         return -1;
715     }
716 
717     // Process our key and add it to the command
718     cluster_multi_add(&mc, kv.key, kv.key_len);
719 
720     // Free key if we prefixed
721     if (kv.key_free) efree(kv.key);
722 
723     // Move to the next key
724     zend_hash_move_forward_ex(ht_arr, &ptr);
725 
726     // Iterate over keys 2...N
727     slot = kv.slot;
728     while (zend_hash_has_more_elements_ex(ht_arr, &ptr) ==SUCCESS) {
729         if (get_key_ht(c, ht_arr, &ptr, &kv) < 0) {
730             cluster_multi_free(&mc);
731             if (ht_free) {
732                 zend_hash_destroy(ht_arr);
733                 efree(ht_arr);
734             }
735             efree(z_args);
736             return -1;
737         }
738 
739         // If the slots have changed, kick off the keys we've aggregated
740         if (slot != kv.slot) {
741             // Process this batch of MGET keys
742             if (distcmd_resp_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, slot,
743                                     &mc, z_ret, i == argc, cb) < 0)
744             {
745                 cluster_multi_free(&mc);
746                 if (ht_free) {
747                     zend_hash_destroy(ht_arr);
748                     efree(ht_arr);
749                 }
750                 efree(z_args);
751                 return -1;
752             }
753         }
754 
755         // Add this key to the command
756         cluster_multi_add(&mc, kv.key, kv.key_len);
757 
758         // Free key if we prefixed
759         if (kv.key_free) efree(kv.key);
760 
761         // Update the last slot we encountered, and the key we're on
762         slot = kv.slot;
763         i++;
764 
765         zend_hash_move_forward_ex(ht_arr, &ptr);
766     }
767     efree(z_args);
768 
769     // If we've got straggler(s) process them
770     if (mc.argc > 0) {
771         if (distcmd_resp_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, slot,
772                                 &mc, z_ret, 1, cb) < 0)
773         {
774             cluster_multi_free(&mc);
775             if (ht_free) {
776                 zend_hash_destroy(ht_arr);
777                 efree(ht_arr);
778             }
779             return -1;
780         }
781     }
782 
783     // Free our command
784     cluster_multi_free(&mc);
785 
786     /* Clean up our hash table if we constructed it from variadic args */
787     if (ht_free) {
788         zend_hash_destroy(ht_arr);
789         efree(ht_arr);
790     }
791 
792     /* Return our object if we're in MULTI mode */
793     if (!CLUSTER_IS_ATOMIC(c))
794         RETVAL_ZVAL(getThis(), 1, 0);
795 
796     // Success
797     return 0;
798 }
799 
800 /* Handler for both MSET and MSETNX */
cluster_mset_cmd(INTERNAL_FUNCTION_PARAMETERS,char * kw,int kw_len,zval * z_ret,cluster_cb cb)801 static int cluster_mset_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len,
802                             zval *z_ret, cluster_cb cb)
803 {
804     redisCluster *c = GET_CONTEXT();
805     clusterKeyValHT kv;
806     clusterMultiCmd mc = {0};
807     zval *z_arr;
808     HashTable *ht_arr;
809     HashPosition ptr;
810     int i = 1, argc;
811     short slot;
812 
813     // Parse our arguments
814     if (zend_parse_parameters(ZEND_NUM_ARGS(), "a", &z_arr) == FAILURE) {
815         return -1;
816     }
817 
818     // No reason to send zero args
819     ht_arr = Z_ARRVAL_P(z_arr);
820     if ((argc = zend_hash_num_elements(ht_arr)) == 0) {
821         return -1;
822     }
823 
824     /* This is a write command */
825     c->readonly = 0;
826 
827     // Set up our multi command handler
828     CLUSTER_MULTI_INIT(mc, kw, kw_len);
829 
830     // Process the first key/value pair outside of our loop
831     zend_hash_internal_pointer_reset_ex(ht_arr, &ptr);
832     if (get_key_val_ht(c, ht_arr, &ptr, &kv) ==-1) return -1;
833     zend_hash_move_forward_ex(ht_arr, &ptr);
834 
835     // Add this to our multi cmd, set slot, free key if we prefixed
836     cluster_multi_add(&mc, kv.key, kv.key_len);
837     cluster_multi_add(&mc, kv.val, kv.val_len);
838     if (kv.key_free) efree(kv.key);
839     if (kv.val_free) efree(kv.val);
840 
841     // While we've got more keys to set
842     slot = kv.slot;
843     while (zend_hash_has_more_elements_ex(ht_arr, &ptr) ==SUCCESS) {
844         // Pull the next key/value pair
845         if (get_key_val_ht(c, ht_arr, &ptr, &kv) ==-1) {
846             return -1;
847         }
848 
849         // If the slots have changed, process responses
850         if (slot != kv.slot) {
851             if (distcmd_resp_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c,
852                                     slot, &mc, z_ret, i == argc, cb) < 0)
853             {
854                 cluster_multi_free(&mc);
855                 return -1;
856             }
857         }
858 
859         // Add this key and value to our command
860         cluster_multi_add(&mc, kv.key, kv.key_len);
861         cluster_multi_add(&mc, kv.val, kv.val_len);
862 
863         // Free our key and value if we need to
864         if (kv.key_free) efree(kv.key);
865         if (kv.val_free) efree(kv.val);
866 
867         // Update our slot, increment position
868         slot = kv.slot;
869         i++;
870 
871         // Move on
872         zend_hash_move_forward_ex(ht_arr, &ptr);
873     }
874 
875     // If we've got stragglers, process them too
876     if (mc.argc > 0) {
877         if (distcmd_resp_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, slot, &mc,
878                                 z_ret, 1, cb) < 0)
879         {
880             cluster_multi_free(&mc);
881             return -1;
882         }
883     }
884 
885     // Free our command
886     cluster_multi_free(&mc);
887 
888     /* Return our object if we're in MULTI mode */
889     if (!CLUSTER_IS_ATOMIC(c))
890         RETVAL_ZVAL(getThis(), 1, 0);
891 
892     // Success
893     return 0;
894 }
895 
896 /* Generic passthru for DEL and UNLINK which act identically */
cluster_generic_delete(INTERNAL_FUNCTION_PARAMETERS,char * kw,int kw_len)897 static void cluster_generic_delete(INTERNAL_FUNCTION_PARAMETERS,
898                                    char *kw, int kw_len)
899 {
900     zval *z_ret = emalloc(sizeof(*z_ret));
901 
902     // Initialize a LONG value to zero for our return
903     ZVAL_LONG(z_ret, 0);
904 
905     // Parse args, process
906     if (cluster_mkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, kw, kw_len, z_ret,
907                         cluster_del_resp) < 0)
908     {
909         efree(z_ret);
910         RETURN_FALSE;
911     }
912 }
913 
914 /* {{{ proto array RedisCluster::del(string key1, string key2, ... keyN) */
PHP_METHOD(RedisCluster,del)915 PHP_METHOD(RedisCluster, del) {
916     cluster_generic_delete(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DEL", sizeof("DEL") - 1);
917 }
918 
919 /* {{{ proto array RedisCluster::unlink(string key1, string key2, ... keyN) */
PHP_METHOD(RedisCluster,unlink)920 PHP_METHOD(RedisCluster, unlink) {
921     cluster_generic_delete(INTERNAL_FUNCTION_PARAM_PASSTHRU, "UNLINK", sizeof("UNLINK") - 1);
922 }
923 
924 /* {{{ proto array RedisCluster::mget(array keys) */
PHP_METHOD(RedisCluster,mget)925 PHP_METHOD(RedisCluster, mget) {
926     zval *z_ret = emalloc(sizeof(*z_ret));
927 
928     array_init(z_ret);
929 
930     // Parse args, process
931     if (cluster_mkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "MGET",
932                         sizeof("MGET")-1, z_ret, cluster_mbulk_mget_resp) < 0)
933     {
934         zval_dtor(z_ret);
935         efree(z_ret);
936         RETURN_FALSE;
937     }
938 }
939 
940 /* {{{ proto bool RedisCluster::mset(array keyvalues) */
PHP_METHOD(RedisCluster,mset)941 PHP_METHOD(RedisCluster, mset) {
942     zval *z_ret = emalloc(sizeof(*z_ret));
943 
944     ZVAL_TRUE(z_ret);
945 
946     // Parse args and process.  If we get a failure, free zval and return FALSE.
947     if (cluster_mset_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "MSET",
948                         sizeof("MSET")-1, z_ret, cluster_mset_resp) ==-1)
949     {
950         efree(z_ret);
951         RETURN_FALSE;
952     }
953 }
954 
955 /* {{{ proto array RedisCluster::msetnx(array keyvalues) */
PHP_METHOD(RedisCluster,msetnx)956 PHP_METHOD(RedisCluster, msetnx) {
957     zval *z_ret = emalloc(sizeof(*z_ret));
958 
959     array_init(z_ret);
960 
961     // Parse args and process.  If we get a failure, free mem and return FALSE
962     if (cluster_mset_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "MSETNX",
963                          sizeof("MSETNX")-1, z_ret, cluster_msetnx_resp) ==-1)
964     {
965         zval_dtor(z_ret);
966         efree(z_ret);
967         RETURN_FALSE;
968     }
969 }
970 /* }}} */
971 
972 /* {{{ proto bool RedisCluster::setex(string key, string value, int expiry) */
PHP_METHOD(RedisCluster,setex)973 PHP_METHOD(RedisCluster, setex) {
974     CLUSTER_PROCESS_KW_CMD("SETEX", redis_key_long_val_cmd, cluster_bool_resp, 0);
975 }
976 /* }}} */
977 
978 /* {{{ proto bool RedisCluster::psetex(string key, string value, int expiry) */
PHP_METHOD(RedisCluster,psetex)979 PHP_METHOD(RedisCluster, psetex) {
980     CLUSTER_PROCESS_KW_CMD("PSETEX", redis_key_long_val_cmd, cluster_bool_resp, 0);
981 }
982 /* }}} */
983 
984 /* {{{ proto bool RedisCluster::setnx(string key, string value) */
PHP_METHOD(RedisCluster,setnx)985 PHP_METHOD(RedisCluster, setnx) {
986     CLUSTER_PROCESS_KW_CMD("SETNX", redis_kv_cmd, cluster_1_resp, 0);
987 }
988 /* }}} */
989 
990 /* {{{ proto string RedisCluster::getSet(string key, string value) */
PHP_METHOD(RedisCluster,getset)991 PHP_METHOD(RedisCluster, getset) {
992     CLUSTER_PROCESS_KW_CMD("GETSET", redis_kv_cmd, cluster_bulk_resp, 0);
993 }
994 /* }}} */
995 
996 /* {{{ proto int RedisCluster::exists(string key) */
PHP_METHOD(RedisCluster,exists)997 PHP_METHOD(RedisCluster, exists) {
998     CLUSTER_PROCESS_CMD(exists, cluster_long_resp, 1);
999 }
1000 /* }}} */
1001 
1002 /* {{{ proto array Redis::keys(string pattern) */
PHP_METHOD(RedisCluster,keys)1003 PHP_METHOD(RedisCluster, keys) {
1004     redisCluster *c = GET_CONTEXT();
1005     redisClusterNode *node;
1006     size_t pat_len;
1007     char *pat, *cmd;
1008     clusterReply *resp;
1009     int i, cmd_len;
1010 
1011     if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &pat, &pat_len)
1012                              == FAILURE)
1013     {
1014         RETURN_FALSE;
1015     }
1016 
1017     /* Prefix and then build our command */
1018     cmd_len = redis_spprintf(c->flags, NULL, &cmd, "KEYS", "k", pat, pat_len);
1019 
1020     array_init(return_value);
1021 
1022     /* Treat as readonly */
1023     c->readonly = CLUSTER_IS_ATOMIC(c);
1024 
1025     /* Iterate over our known nodes */
1026     ZEND_HASH_FOREACH_PTR(c->nodes, node) {
1027         if (node == NULL) continue;
1028         if (cluster_send_slot(c, node->slot, cmd, cmd_len, TYPE_MULTIBULK
1029                             ) < 0)
1030         {
1031             php_error_docref(0, E_ERROR, "Can't send KEYS to %s:%d",
1032                 ZSTR_VAL(node->sock->host), node->sock->port);
1033             zval_dtor(return_value);
1034             efree(cmd);
1035             RETURN_FALSE;
1036         }
1037 
1038         /* Ensure we can get a response */
1039         resp = cluster_read_resp(c, 0);
1040         if (!resp) {
1041             php_error_docref(0, E_WARNING,
1042                 "Can't read response from %s:%d", ZSTR_VAL(node->sock->host),
1043                 node->sock->port);
1044             continue;
1045         }
1046 
1047         /* Iterate keys, adding to our big array */
1048         for(i = 0; i < resp->elements; i++) {
1049             /* Skip non bulk responses, they should all be bulk */
1050             if (resp->element[i]->type != TYPE_BULK) {
1051                 continue;
1052             }
1053 
1054             add_next_index_stringl(return_value, resp->element[i]->str,
1055                 resp->element[i]->len);
1056         }
1057 
1058         /* Free response, don't free data */
1059         cluster_free_reply(resp, 1);
1060     } ZEND_HASH_FOREACH_END();
1061 
1062     efree(cmd);
1063 }
1064 /* }}} */
1065 
1066 /* {{{ proto int RedisCluster::type(string key) */
PHP_METHOD(RedisCluster,type)1067 PHP_METHOD(RedisCluster, type) {
1068     CLUSTER_PROCESS_KW_CMD("TYPE", redis_key_cmd, cluster_type_resp, 1);
1069 }
1070 /* }}} */
1071 
1072 /* {{{ proto string RedisCluster::pop(string key) */
PHP_METHOD(RedisCluster,lpop)1073 PHP_METHOD(RedisCluster, lpop) {
1074     CLUSTER_PROCESS_KW_CMD("LPOP", redis_key_cmd, cluster_bulk_resp, 0);
1075 }
1076 /* }}} */
1077 
1078 /* {{{ proto string RedisCluster::rpop(string key) */
PHP_METHOD(RedisCluster,rpop)1079 PHP_METHOD(RedisCluster, rpop) {
1080     CLUSTER_PROCESS_KW_CMD("RPOP", redis_key_cmd, cluster_bulk_resp, 0);
1081 }
1082 /* }}} */
1083 
1084 /* {{{ proto bool RedisCluster::lset(string key, long index, string val) */
PHP_METHOD(RedisCluster,lset)1085 PHP_METHOD(RedisCluster, lset) {
1086     CLUSTER_PROCESS_KW_CMD("LSET", redis_key_long_val_cmd, cluster_bool_resp, 0);
1087 }
1088 /* }}} */
1089 
1090 /* {{{ proto string RedisCluster::spop(string key) */
PHP_METHOD(RedisCluster,spop)1091 PHP_METHOD(RedisCluster, spop) {
1092     if (ZEND_NUM_ARGS() == 1) {
1093         CLUSTER_PROCESS_KW_CMD("SPOP", redis_key_cmd, cluster_bulk_resp, 0);
1094     } else if (ZEND_NUM_ARGS() == 2) {
1095         CLUSTER_PROCESS_KW_CMD("SPOP", redis_key_long_cmd, cluster_mbulk_resp, 0);
1096     } else {
1097         ZEND_WRONG_PARAM_COUNT();
1098     }
1099 }
1100 /* }}} */
1101 
1102 /* {{{ proto string|array RedisCluster::srandmember(string key, [long count]) */
PHP_METHOD(RedisCluster,srandmember)1103 PHP_METHOD(RedisCluster, srandmember) {
1104     redisCluster *c = GET_CONTEXT();
1105     cluster_cb cb;
1106     char *cmd; int cmd_len; short slot;
1107     short have_count;
1108 
1109     /* Treat as readonly */
1110     c->readonly = CLUSTER_IS_ATOMIC(c);
1111 
1112     if (redis_srandmember_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags,
1113                              &cmd, &cmd_len, &slot, NULL, &have_count)
1114                              == FAILURE)
1115     {
1116         RETURN_FALSE;
1117     }
1118 
1119     if (cluster_send_command(c,slot,cmd,cmd_len) < 0 || c->err != NULL) {
1120         efree(cmd);
1121         RETURN_FALSE;
1122     }
1123 
1124     // Clean up command
1125     efree(cmd);
1126 
1127     cb = have_count ? cluster_mbulk_resp : cluster_bulk_resp;
1128     if (CLUSTER_IS_ATOMIC(c)) {
1129         cb(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
1130     } else {
1131         void *ctx = NULL;
1132         CLUSTER_ENQUEUE_RESPONSE(c, slot, cb, ctx);
1133         RETURN_ZVAL(getThis(), 1, 0);
1134     }
1135 }
1136 
1137 /* {{{ proto string RedisCluster::strlen(string key) */
PHP_METHOD(RedisCluster,strlen)1138 PHP_METHOD(RedisCluster, strlen) {
1139     CLUSTER_PROCESS_KW_CMD("STRLEN", redis_key_cmd, cluster_long_resp, 1);
1140 }
1141 
1142 /* {{{ proto long RedisCluster::lpush(string key, string val1, ... valN) */
PHP_METHOD(RedisCluster,lpush)1143 PHP_METHOD(RedisCluster, lpush) {
1144     CLUSTER_PROCESS_KW_CMD("LPUSH", redis_key_varval_cmd, cluster_long_resp, 0);
1145 }
1146 /* }}} */
1147 
1148 /* {{{ proto long RedisCluster::rpush(string key, string val1, ... valN) */
PHP_METHOD(RedisCluster,rpush)1149 PHP_METHOD(RedisCluster, rpush) {
1150     CLUSTER_PROCESS_KW_CMD("RPUSH", redis_key_varval_cmd, cluster_long_resp, 0);
1151 }
1152 /* }}} */
1153 
1154 /* {{{ proto array RedisCluster::blpop(string key1, ... keyN, long timeout) */
PHP_METHOD(RedisCluster,blpop)1155 PHP_METHOD(RedisCluster, blpop) {
1156     CLUSTER_PROCESS_KW_CMD("BLPOP", redis_blocking_pop_cmd, cluster_mbulk_resp, 0);
1157 }
1158 /* }}} */
1159 
1160 /* {{{ proto array RedisCluster::brpop(string key1, ... keyN, long timeout */
PHP_METHOD(RedisCluster,brpop)1161 PHP_METHOD(RedisCluster, brpop) {
1162     CLUSTER_PROCESS_KW_CMD("BRPOP", redis_blocking_pop_cmd, cluster_mbulk_resp, 0);
1163 }
1164 /* }}} */
1165 
1166 /* {{{ proto long RedisCluster::rpushx(string key, mixed value) */
PHP_METHOD(RedisCluster,rpushx)1167 PHP_METHOD(RedisCluster, rpushx) {
1168     CLUSTER_PROCESS_KW_CMD("RPUSHX", redis_kv_cmd, cluster_long_resp, 0);
1169 }
1170 /* }}} */
1171 
1172 /* {{{ proto long RedisCluster::lpushx(string key, mixed value) */
PHP_METHOD(RedisCluster,lpushx)1173 PHP_METHOD(RedisCluster, lpushx) {
1174     CLUSTER_PROCESS_KW_CMD("LPUSHX", redis_kv_cmd, cluster_long_resp, 0);
1175 }
1176 /* }}} */
1177 
1178 /* {{{ proto long RedisCluster::linsert(string k,string pos,mix pvt,mix val) */
PHP_METHOD(RedisCluster,linsert)1179 PHP_METHOD(RedisCluster, linsert) {
1180     CLUSTER_PROCESS_CMD(linsert, cluster_long_resp, 0);
1181 }
1182 /* }}} */
1183 
1184 /* {{{ proto string RedisCluster::lindex(string key, long index) */
PHP_METHOD(RedisCluster,lindex)1185 PHP_METHOD(RedisCluster, lindex) {
1186     CLUSTER_PROCESS_KW_CMD("LINDEX", redis_key_long_cmd, cluster_bulk_resp, 0);
1187 }
1188 /* }}} */
1189 
1190 /* {{{ proto long RedisCluster::lrem(string key, long count, string val) */
PHP_METHOD(RedisCluster,lrem)1191 PHP_METHOD(RedisCluster, lrem) {
1192     CLUSTER_PROCESS_CMD(lrem, cluster_long_resp, 0);
1193 }
1194 /* }}} */
1195 
1196 /* {{{ proto string RedisCluster::rpoplpush(string key, string key) */
PHP_METHOD(RedisCluster,rpoplpush)1197 PHP_METHOD(RedisCluster, rpoplpush) {
1198     CLUSTER_PROCESS_KW_CMD("RPOPLPUSH", redis_key_key_cmd, cluster_bulk_resp, 0);
1199 }
1200 /* }}} */
1201 
1202 /* {{{ proto string RedisCluster::brpoplpush(string key, string key, long tm) */
PHP_METHOD(RedisCluster,brpoplpush)1203 PHP_METHOD(RedisCluster, brpoplpush) {
1204     CLUSTER_PROCESS_CMD(brpoplpush, cluster_bulk_resp, 0);
1205 }
1206 /* }}} */
1207 
1208 /* {{{ proto long RedisCluster::llen(string key)  */
PHP_METHOD(RedisCluster,llen)1209 PHP_METHOD(RedisCluster, llen) {
1210     CLUSTER_PROCESS_KW_CMD("LLEN", redis_key_cmd, cluster_long_resp, 1);
1211 }
1212 /* }}} */
1213 
1214 /* {{{ proto long RedisCluster::scard(string key) */
PHP_METHOD(RedisCluster,scard)1215 PHP_METHOD(RedisCluster, scard) {
1216     CLUSTER_PROCESS_KW_CMD("SCARD", redis_key_cmd, cluster_long_resp, 1);
1217 }
1218 /* }}} */
1219 
1220 /* {{{ proto array RedisCluster::smembers(string key) */
PHP_METHOD(RedisCluster,smembers)1221 PHP_METHOD(RedisCluster, smembers) {
1222     CLUSTER_PROCESS_KW_CMD("SMEMBERS", redis_key_cmd, cluster_mbulk_resp, 1);
1223 }
1224 /* }}} */
1225 
1226 /* {{{ proto long RedisCluster::sismember(string key) */
PHP_METHOD(RedisCluster,sismember)1227 PHP_METHOD(RedisCluster, sismember) {
1228     CLUSTER_PROCESS_KW_CMD("SISMEMBER", redis_kv_cmd, cluster_1_resp, 1);
1229 }
1230 /* }}} */
1231 
1232 /* {{{ proto long RedisCluster::sadd(string key, string val1 [, ...]) */
PHP_METHOD(RedisCluster,sadd)1233 PHP_METHOD(RedisCluster, sadd) {
1234     CLUSTER_PROCESS_KW_CMD("SADD", redis_key_varval_cmd, cluster_long_resp, 0);
1235 }
1236 /* }}} */
1237 
1238 /* {{{ proto long RedisCluster::saddarray(string key, array values) */
PHP_METHOD(RedisCluster,saddarray)1239 PHP_METHOD(RedisCluster, saddarray) {
1240     CLUSTER_PROCESS_KW_CMD("SADD", redis_key_val_arr_cmd, cluster_long_resp, 0);
1241 }
1242 /* }}} */
1243 
1244 /* {{{ proto long RedisCluster::srem(string key, string val1 [, ...]) */
PHP_METHOD(RedisCluster,srem)1245 PHP_METHOD(RedisCluster, srem) {
1246     CLUSTER_PROCESS_KW_CMD("SREM", redis_key_varval_cmd, cluster_long_resp, 0);
1247 }
1248 /* }}} */
1249 
1250 /* {{{ proto array RedisCluster::sunion(string key1, ... keyN) */
PHP_METHOD(RedisCluster,sunion)1251 PHP_METHOD(RedisCluster, sunion) {
1252     CLUSTER_PROCESS_CMD(sunion, cluster_mbulk_resp, 0);
1253 }
1254 /* }}} */
1255 
1256 /* {{{ proto long RedisCluster::sunionstore(string dst, string k1, ... kN) */
PHP_METHOD(RedisCluster,sunionstore)1257 PHP_METHOD(RedisCluster, sunionstore) {
1258     CLUSTER_PROCESS_CMD(sunionstore, cluster_long_resp, 0);
1259 }
1260 /* }}} */
1261 
1262 /* {{{ ptoto array RedisCluster::sinter(string k1, ... kN) */
PHP_METHOD(RedisCluster,sinter)1263 PHP_METHOD(RedisCluster, sinter) {
1264     CLUSTER_PROCESS_CMD(sinter, cluster_mbulk_resp, 0);
1265 }
1266 /* }}} */
1267 
1268 /* {{{ ptoto long RedisCluster::sinterstore(string dst, string k1, ... kN) */
PHP_METHOD(RedisCluster,sinterstore)1269 PHP_METHOD(RedisCluster, sinterstore) {
1270     CLUSTER_PROCESS_CMD(sinterstore, cluster_long_resp, 0);
1271 }
1272 /* }}} */
1273 
1274 /* {{{ proto array RedisCluster::sdiff(string k1, ... kN) */
PHP_METHOD(RedisCluster,sdiff)1275 PHP_METHOD(RedisCluster, sdiff) {
1276     CLUSTER_PROCESS_CMD(sdiff, cluster_mbulk_resp, 1);
1277 }
1278 /* }}} */
1279 
1280 /* {{{ proto long RedisCluster::sdiffstore(string dst, string k1, ... kN) */
PHP_METHOD(RedisCluster,sdiffstore)1281 PHP_METHOD(RedisCluster, sdiffstore) {
1282     CLUSTER_PROCESS_CMD(sdiffstore, cluster_long_resp, 0);
1283 }
1284 /* }}} */
1285 
1286 /* {{{ proto bool RedisCluster::smove(sting src, string dst, string mem) */
PHP_METHOD(RedisCluster,smove)1287 PHP_METHOD(RedisCluster, smove) {
1288     CLUSTER_PROCESS_CMD(smove, cluster_1_resp, 0);
1289 }
1290 /* }}} */
1291 
1292 /* {{{ proto bool RedisCluster::persist(string key) */
PHP_METHOD(RedisCluster,persist)1293 PHP_METHOD(RedisCluster, persist) {
1294     CLUSTER_PROCESS_KW_CMD("PERSIST", redis_key_cmd, cluster_1_resp, 0);
1295 }
1296 /* }}} */
1297 
1298 /* {{{ proto long RedisCluster::ttl(string key) */
PHP_METHOD(RedisCluster,ttl)1299 PHP_METHOD(RedisCluster, ttl) {
1300     CLUSTER_PROCESS_KW_CMD("TTL", redis_key_cmd, cluster_long_resp, 1);
1301 }
1302 /* }}} */
1303 
1304 /* {{{ proto long RedisCluster::pttl(string key) */
PHP_METHOD(RedisCluster,pttl)1305 PHP_METHOD(RedisCluster, pttl) {
1306     CLUSTER_PROCESS_KW_CMD("PTTL", redis_key_cmd, cluster_long_resp, 1);
1307 }
1308 /* }}} */
1309 
1310 /* {{{ proto long RedisCluster::zcard(string key) */
PHP_METHOD(RedisCluster,zcard)1311 PHP_METHOD(RedisCluster, zcard) {
1312     CLUSTER_PROCESS_KW_CMD("ZCARD", redis_key_cmd, cluster_long_resp, 1);
1313 }
1314 /* }}} */
1315 
1316 /* {{{ proto double RedisCluster::zscore(string key) */
PHP_METHOD(RedisCluster,zscore)1317 PHP_METHOD(RedisCluster, zscore) {
1318     CLUSTER_PROCESS_KW_CMD("ZSCORE", redis_kv_cmd, cluster_dbl_resp, 1);
1319 }
1320 /* }}} */
1321 
1322 /* {{{ proto long RedisCluster::zadd(string key,double score,string mem, ...) */
PHP_METHOD(RedisCluster,zadd)1323 PHP_METHOD(RedisCluster, zadd) {
1324     CLUSTER_PROCESS_CMD(zadd, cluster_long_resp, 0);
1325 }
1326 /* }}} */
1327 
1328 /* {{{ proto double RedisCluster::zincrby(string key, double by, string mem) */
PHP_METHOD(RedisCluster,zincrby)1329 PHP_METHOD(RedisCluster, zincrby) {
1330     CLUSTER_PROCESS_CMD(zincrby, cluster_dbl_resp, 0);
1331 }
1332 /* }}} */
1333 
1334 /* {{{ proto RedisCluster::zremrangebyscore(string k, string s, string e) */
PHP_METHOD(RedisCluster,zremrangebyscore)1335 PHP_METHOD(RedisCluster, zremrangebyscore) {
1336     CLUSTER_PROCESS_KW_CMD("ZREMRANGEBYSCORE", redis_key_str_str_cmd,
1337         cluster_long_resp, 0);
1338 }
1339 /* }}} */
1340 
1341 /* {{{ proto RedisCluster::zcount(string key, string s, string e) */
PHP_METHOD(RedisCluster,zcount)1342 PHP_METHOD(RedisCluster, zcount) {
1343     CLUSTER_PROCESS_KW_CMD("ZCOUNT", redis_key_str_str_cmd, cluster_long_resp, 1);
1344 }
1345 /* }}} */
1346 
1347 /* {{{ proto long RedisCluster::zrank(string key, mixed member) */
PHP_METHOD(RedisCluster,zrank)1348 PHP_METHOD(RedisCluster, zrank) {
1349     CLUSTER_PROCESS_KW_CMD("ZRANK", redis_kv_cmd, cluster_long_resp, 1);
1350 }
1351 /* }}} */
1352 
1353 /* {{{ proto long RedisCluster::zrevrank(string key, mixed member) */
PHP_METHOD(RedisCluster,zrevrank)1354 PHP_METHOD(RedisCluster, zrevrank) {
1355     CLUSTER_PROCESS_KW_CMD("ZREVRANK", redis_kv_cmd, cluster_long_resp, 1);
1356 }
1357 /* }}} */
1358 
1359 /* {{{ proto long RedisCluster::hlen(string key) */
PHP_METHOD(RedisCluster,hlen)1360 PHP_METHOD(RedisCluster, hlen) {
1361     CLUSTER_PROCESS_KW_CMD("HLEN", redis_key_cmd, cluster_long_resp, 1);
1362 }
1363 /* }}} */
1364 
1365 /* {{{ proto array RedisCluster::hkeys(string key) */
PHP_METHOD(RedisCluster,hkeys)1366 PHP_METHOD(RedisCluster, hkeys) {
1367     CLUSTER_PROCESS_KW_CMD("HKEYS", redis_key_cmd, cluster_mbulk_raw_resp, 1);
1368 }
1369 /* }}} */
1370 
1371 /* {{{ proto array RedisCluster::hvals(string key) */
PHP_METHOD(RedisCluster,hvals)1372 PHP_METHOD(RedisCluster, hvals) {
1373     CLUSTER_PROCESS_KW_CMD("HVALS", redis_key_cmd, cluster_mbulk_resp, 1);
1374 }
1375 /* }}} */
1376 
1377 /* {{{ proto string RedisCluster::hget(string key, string mem) */
PHP_METHOD(RedisCluster,hget)1378 PHP_METHOD(RedisCluster, hget) {
1379     CLUSTER_PROCESS_KW_CMD("HGET", redis_key_str_cmd, cluster_bulk_resp, 1);
1380 }
1381 /* }}} */
1382 
1383 /* {{{ proto bool RedisCluster::hset(string key, string mem, string val) */
PHP_METHOD(RedisCluster,hset)1384 PHP_METHOD(RedisCluster, hset) {
1385     CLUSTER_PROCESS_CMD(hset, cluster_long_resp, 0);
1386 }
1387 /* }}} */
1388 
1389 /* {{{ proto bool RedisCluster::hsetnx(string key, string mem, string val) */
PHP_METHOD(RedisCluster,hsetnx)1390 PHP_METHOD(RedisCluster, hsetnx) {
1391     CLUSTER_PROCESS_CMD(hsetnx, cluster_1_resp, 0);
1392 }
1393 /* }}} */
1394 
1395 /* {{{ proto array RedisCluster::hgetall(string key) */
PHP_METHOD(RedisCluster,hgetall)1396 PHP_METHOD(RedisCluster, hgetall) {
1397     CLUSTER_PROCESS_KW_CMD("HGETALL", redis_key_cmd,
1398         cluster_mbulk_zipstr_resp, 1);
1399 }
1400 /* }}} */
1401 
1402 /* {{{ proto bool RedisCluster::hexists(string key, string member) */
PHP_METHOD(RedisCluster,hexists)1403 PHP_METHOD(RedisCluster, hexists) {
1404     CLUSTER_PROCESS_KW_CMD("HEXISTS", redis_key_str_cmd, cluster_1_resp, 1);
1405 }
1406 /* }}} */
1407 
1408 /* {{{ proto long RedisCluster::hincr(string key, string mem, long val) */
PHP_METHOD(RedisCluster,hincrby)1409 PHP_METHOD(RedisCluster, hincrby) {
1410     CLUSTER_PROCESS_CMD(hincrby, cluster_long_resp, 0);
1411 }
1412 /* }}} */
1413 
1414 /* {{{ proto double RedisCluster::hincrbyfloat(string k, string m, double v) */
PHP_METHOD(RedisCluster,hincrbyfloat)1415 PHP_METHOD(RedisCluster, hincrbyfloat) {
1416     CLUSTER_PROCESS_CMD(hincrbyfloat, cluster_dbl_resp, 0);
1417 }
1418 /* }}} */
1419 
1420 /* {{{ proto bool RedisCluster::hmset(string key, array key_vals) */
PHP_METHOD(RedisCluster,hmset)1421 PHP_METHOD(RedisCluster, hmset) {
1422     CLUSTER_PROCESS_CMD(hmset, cluster_bool_resp, 0);
1423 }
1424 /* }}} */
1425 
1426 /* {{{ proto long RedisCluster::hdel(string key, string mem1, ... memN) */
PHP_METHOD(RedisCluster,hdel)1427 PHP_METHOD(RedisCluster, hdel) {
1428     CLUSTER_PROCESS_CMD(hdel, cluster_long_resp, 0);
1429 }
1430 /* }}} */
1431 
1432 /* {{{ proto array RedisCluster::hmget(string key, array members) */
PHP_METHOD(RedisCluster,hmget)1433 PHP_METHOD(RedisCluster, hmget) {
1434     CLUSTER_PROCESS_CMD(hmget, cluster_mbulk_assoc_resp, 1);
1435 }
1436 /* }}} */
1437 
1438 /* {{{ proto array RedisCluster::hstrlen(string key, string field) */
PHP_METHOD(RedisCluster,hstrlen)1439 PHP_METHOD(RedisCluster, hstrlen) {
1440     CLUSTER_PROCESS_CMD(hstrlen, cluster_long_resp, 1);
1441 }
1442 /* }}} */
1443 
1444 
1445 /* {{{ proto string RedisCluster::dump(string key) */
PHP_METHOD(RedisCluster,dump)1446 PHP_METHOD(RedisCluster, dump) {
1447     CLUSTER_PROCESS_KW_CMD("DUMP", redis_key_cmd, cluster_bulk_raw_resp, 1);
1448 }
1449 
1450 /* {{{ proto long RedisCluster::incr(string key) */
PHP_METHOD(RedisCluster,incr)1451 PHP_METHOD(RedisCluster, incr) {
1452     CLUSTER_PROCESS_CMD(incr, cluster_long_resp, 0);
1453 }
1454 /* }}} */
1455 
1456 /* {{{ proto long RedisCluster::incrby(string key, long byval) */
PHP_METHOD(RedisCluster,incrby)1457 PHP_METHOD(RedisCluster, incrby) {
1458     CLUSTER_PROCESS_KW_CMD("INCRBY", redis_key_long_cmd, cluster_long_resp, 0);
1459 }
1460 /* }}} */
1461 
1462 /* {{{ proto long RedisCluster::decr(string key) */
PHP_METHOD(RedisCluster,decr)1463 PHP_METHOD(RedisCluster, decr) {
1464     CLUSTER_PROCESS_CMD(decr, cluster_long_resp, 0);
1465 }
1466 /* }}} */
1467 
1468 /* {{{ proto long RedisCluster::decrby(string key, long byval) */
PHP_METHOD(RedisCluster,decrby)1469 PHP_METHOD(RedisCluster, decrby) {
1470     CLUSTER_PROCESS_KW_CMD("DECRBY", redis_key_long_cmd, cluster_long_resp, 0);
1471 }
1472 /* }}} */
1473 
1474 /* {{{ proto double RedisCluster::incrbyfloat(string key, double val) */
PHP_METHOD(RedisCluster,incrbyfloat)1475 PHP_METHOD(RedisCluster, incrbyfloat) {
1476     CLUSTER_PROCESS_KW_CMD("INCRBYFLOAT", redis_key_dbl_cmd,
1477         cluster_dbl_resp, 0);
1478 }
1479 /* }}} */
1480 
1481 /* {{{ proto double RedisCluster::decrbyfloat(string key, double val) */
PHP_METHOD(RedisCluster,decrbyfloat)1482 PHP_METHOD(RedisCluster, decrbyfloat) {
1483     CLUSTER_PROCESS_KW_CMD("DECRBYFLOAT", redis_key_dbl_cmd,
1484         cluster_dbl_resp, 0);
1485 }
1486 /* }}} */
1487 
1488 /* {{{ proto bool RedisCluster::expire(string key, long sec) */
PHP_METHOD(RedisCluster,expire)1489 PHP_METHOD(RedisCluster, expire) {
1490     CLUSTER_PROCESS_KW_CMD("EXPIRE", redis_key_long_cmd, cluster_1_resp, 0);
1491 }
1492 /* }}} */
1493 
1494 /* {{{ proto bool RedisCluster::expireat(string key, long ts) */
PHP_METHOD(RedisCluster,expireat)1495 PHP_METHOD(RedisCluster, expireat) {
1496     CLUSTER_PROCESS_KW_CMD("EXPIREAT", redis_key_long_cmd, cluster_1_resp, 0);
1497 }
1498 
1499 /* {{{ proto bool RedisCluster::pexpire(string key, long ms) */
PHP_METHOD(RedisCluster,pexpire)1500 PHP_METHOD(RedisCluster, pexpire) {
1501     CLUSTER_PROCESS_KW_CMD("PEXPIRE", redis_key_long_cmd, cluster_1_resp, 0);
1502 }
1503 /* }}} */
1504 
1505 /* {{{ proto bool RedisCluster::pexpireat(string key, long ts) */
PHP_METHOD(RedisCluster,pexpireat)1506 PHP_METHOD(RedisCluster, pexpireat) {
1507     CLUSTER_PROCESS_KW_CMD("PEXPIREAT", redis_key_long_cmd, cluster_1_resp, 0);
1508 }
1509 /* }}} */
1510 
1511 /* {{{ proto long RedisCluster::append(string key, string val) */
PHP_METHOD(RedisCluster,append)1512 PHP_METHOD(RedisCluster, append) {
1513     CLUSTER_PROCESS_KW_CMD("APPEND", redis_kv_cmd, cluster_long_resp, 0);
1514 }
1515 /* }}} */
1516 
1517 /* {{{ proto long RedisCluster::getbit(string key, long val) */
PHP_METHOD(RedisCluster,getbit)1518 PHP_METHOD(RedisCluster, getbit) {
1519     CLUSTER_PROCESS_KW_CMD("GETBIT", redis_key_long_cmd, cluster_long_resp, 1);
1520 }
1521 /* }}} */
1522 
1523 /* {{{ proto long RedisCluster::setbit(string key, long offset, bool onoff) */
PHP_METHOD(RedisCluster,setbit)1524 PHP_METHOD(RedisCluster, setbit) {
1525     CLUSTER_PROCESS_CMD(setbit, cluster_long_resp, 0);
1526 }
1527 
1528 /* {{{ proto long RedisCluster::bitop(string op,string key,[string key2,...]) */
PHP_METHOD(RedisCluster,bitop)1529 PHP_METHOD(RedisCluster, bitop)
1530 {
1531     CLUSTER_PROCESS_CMD(bitop, cluster_long_resp, 0);
1532 }
1533 /* }}} */
1534 
1535 /* {{{ proto long RedisCluster::bitcount(string key, [int start, int end]) */
PHP_METHOD(RedisCluster,bitcount)1536 PHP_METHOD(RedisCluster, bitcount) {
1537     CLUSTER_PROCESS_CMD(bitcount, cluster_long_resp, 1);
1538 }
1539 /* }}} */
1540 
1541 /* {{{ proto long RedisCluster::bitpos(string key, int bit, [int s, int end]) */
PHP_METHOD(RedisCluster,bitpos)1542 PHP_METHOD(RedisCluster, bitpos) {
1543     CLUSTER_PROCESS_CMD(bitpos, cluster_long_resp, 1);
1544 }
1545 /* }}} */
1546 
1547 /* {{{ proto string Redis::lget(string key, long index) */
PHP_METHOD(RedisCluster,lget)1548 PHP_METHOD(RedisCluster, lget) {
1549     CLUSTER_PROCESS_KW_CMD("LINDEX", redis_key_long_cmd, cluster_bulk_resp, 1);
1550 }
1551 /* }}} */
1552 
1553 /* {{{ proto string RedisCluster::getrange(string key, long start, long end) */
PHP_METHOD(RedisCluster,getrange)1554 PHP_METHOD(RedisCluster, getrange) {
1555     CLUSTER_PROCESS_KW_CMD("GETRANGE", redis_key_long_long_cmd,
1556         cluster_bulk_resp, 1);
1557 }
1558 /* }}} */
1559 
1560 /* {{{ proto string RedisCluster::ltrim(string key, long start, long end) */
PHP_METHOD(RedisCluster,ltrim)1561 PHP_METHOD(RedisCluster, ltrim) {
1562     CLUSTER_PROCESS_KW_CMD("LTRIM", redis_key_long_long_cmd, cluster_bool_resp, 0);
1563 }
1564 /* }}} */
1565 
1566 /* {{{ proto array RedisCluster::lrange(string key, long start, long end) */
PHP_METHOD(RedisCluster,lrange)1567 PHP_METHOD(RedisCluster, lrange) {
1568     CLUSTER_PROCESS_KW_CMD("LRANGE", redis_key_long_long_cmd,
1569         cluster_mbulk_resp, 1);
1570 }
1571 /* }}} */
1572 
1573 /* {{{ proto long RedisCluster::zremrangebyrank(string k, long s, long e) */
PHP_METHOD(RedisCluster,zremrangebyrank)1574 PHP_METHOD(RedisCluster, zremrangebyrank) {
1575     CLUSTER_PROCESS_KW_CMD("ZREMRANGEBYRANK", redis_key_long_long_cmd,
1576         cluster_long_resp, 0);
1577 }
1578 /* }}} */
1579 
1580 /* {{{ proto long RedisCluster::publish(string key, string msg) */
PHP_METHOD(RedisCluster,publish)1581 PHP_METHOD(RedisCluster, publish) {
1582     CLUSTER_PROCESS_KW_CMD("PUBLISH", redis_key_str_cmd, cluster_long_resp, 0);
1583 }
1584 /* }}} */
1585 
1586 /* {{{ proto bool RedisCluster::rename(string key1, string key2) */
PHP_METHOD(RedisCluster,rename)1587 PHP_METHOD(RedisCluster, rename) {
1588     CLUSTER_PROCESS_KW_CMD("RENAME", redis_key_key_cmd, cluster_bool_resp, 0);
1589 }
1590 /* }}} */
1591 
1592 /* {{{ proto bool RedisCluster::renamenx(string key1, string key2) */
PHP_METHOD(RedisCluster,renamenx)1593 PHP_METHOD(RedisCluster, renamenx) {
1594     CLUSTER_PROCESS_KW_CMD("RENAMENX", redis_key_key_cmd, cluster_1_resp, 0);
1595 }
1596 /* }}} */
1597 
1598 /* {{{ proto long RedisCluster::pfcount(string key) */
PHP_METHOD(RedisCluster,pfcount)1599 PHP_METHOD(RedisCluster, pfcount) {
1600     CLUSTER_PROCESS_CMD(pfcount, cluster_long_resp, 1);
1601 }
1602 /* }}} */
1603 
1604 /* {{{ proto bool RedisCluster::pfadd(string key, array vals) */
PHP_METHOD(RedisCluster,pfadd)1605 PHP_METHOD(RedisCluster, pfadd) {
1606     CLUSTER_PROCESS_CMD(pfadd, cluster_1_resp, 0);
1607 }
1608 /* }}} */
1609 
1610 /* {{{ proto bool RedisCluster::pfmerge(string key, array keys) */
PHP_METHOD(RedisCluster,pfmerge)1611 PHP_METHOD(RedisCluster, pfmerge) {
1612     CLUSTER_PROCESS_CMD(pfmerge, cluster_bool_resp, 0);
1613 }
1614 /* }}} */
1615 
1616 /* {{{ proto boolean RedisCluster::restore(string key, long ttl, string val) */
PHP_METHOD(RedisCluster,restore)1617 PHP_METHOD(RedisCluster, restore) {
1618     CLUSTER_PROCESS_KW_CMD("RESTORE", redis_key_long_str_cmd,
1619         cluster_bool_resp, 0);
1620 }
1621 /* }}} */
1622 
1623 /* {{{ proto long RedisCluster::setrange(string key, long offset, string val) */
PHP_METHOD(RedisCluster,setrange)1624 PHP_METHOD(RedisCluster, setrange) {
1625     CLUSTER_PROCESS_KW_CMD("SETRANGE", redis_key_long_str_cmd,
1626         cluster_long_resp, 0);
1627 }
1628 /* }}} */
1629 
1630 /* Generic implementation for ZRANGE, ZREVRANGE, ZRANGEBYSCORE, ZREVRANGEBYSCORE */
generic_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS,char * kw,zrange_cb fun)1631 static void generic_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw,
1632                                zrange_cb fun)
1633 {
1634     redisCluster *c = GET_CONTEXT();
1635     c->readonly = CLUSTER_IS_ATOMIC(c);
1636     cluster_cb cb;
1637     char *cmd; int cmd_len; short slot;
1638     int withscores = 0;
1639 
1640     if (fun(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, kw, &cmd, &cmd_len,
1641            &withscores, &slot, NULL) == FAILURE)
1642     {
1643         efree(cmd);
1644         RETURN_FALSE;
1645     }
1646 
1647     if (cluster_send_command(c,slot,cmd,cmd_len) < 0 || c->err != NULL) {
1648         efree(cmd);
1649         RETURN_FALSE;
1650     }
1651 
1652     efree(cmd);
1653 
1654     cb = withscores ? cluster_mbulk_zipdbl_resp : cluster_mbulk_resp;
1655     if (CLUSTER_IS_ATOMIC(c)) {
1656         cb(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
1657     } else {
1658         void *ctx = NULL;
1659         CLUSTER_ENQUEUE_RESPONSE(c, slot, cb, ctx);
1660         RETURN_ZVAL(getThis(), 1, 0);
1661     }
1662 }
1663 
1664 /* {{{ proto
1665  *     array RedisCluster::zrange(string k, long s, long e, bool score = 0) */
PHP_METHOD(RedisCluster,zrange)1666 PHP_METHOD(RedisCluster, zrange) {
1667     generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZRANGE",
1668         redis_zrange_cmd);
1669 }
1670 /* }}} */
1671 
1672 /* {{{ proto
1673  *     array RedisCluster::zrevrange(string k,long s,long e,bool scores = 0) */
PHP_METHOD(RedisCluster,zrevrange)1674 PHP_METHOD(RedisCluster, zrevrange) {
1675     generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZREVRANGE",
1676         redis_zrange_cmd);
1677 }
1678 /* }}} */
1679 
1680 /* {{{ proto array
1681  *     RedisCluster::zrangebyscore(string k, long s, long e, array opts) */
PHP_METHOD(RedisCluster,zrangebyscore)1682 PHP_METHOD(RedisCluster, zrangebyscore) {
1683     generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZRANGEBYSCORE",
1684         redis_zrangebyscore_cmd);
1685 }
1686 /* }}} */
1687 
1688 /* {{{ proto RedisCluster::zunionstore(string dst, array keys, [array weights,
1689  *                                     string agg]) */
PHP_METHOD(RedisCluster,zunionstore)1690 PHP_METHOD(RedisCluster, zunionstore) {
1691     CLUSTER_PROCESS_KW_CMD("ZUNIONSTORE", redis_zinter_cmd, cluster_long_resp, 0);
1692 }
1693 /* }}} */
1694 
1695 /* {{{ proto RedisCluster::zinterstore(string dst, array keys, [array weights,
1696  *                                     string agg]) */
PHP_METHOD(RedisCluster,zinterstore)1697 PHP_METHOD(RedisCluster, zinterstore) {
1698     CLUSTER_PROCESS_KW_CMD("ZINTERSTORE", redis_zinter_cmd, cluster_long_resp, 0);
1699 }
1700 /* }}} */
1701 
1702 /* {{{ proto RedisCluster::zrem(string key, string val1, ... valN) */
PHP_METHOD(RedisCluster,zrem)1703 PHP_METHOD(RedisCluster, zrem) {
1704     CLUSTER_PROCESS_KW_CMD("ZREM", redis_key_varval_cmd, cluster_long_resp, 0);
1705 }
1706 /* }}} */
1707 
1708 /* {{{ proto array
1709  *     RedisCluster::zrevrangebyscore(string k, long s, long e, array opts) */
PHP_METHOD(RedisCluster,zrevrangebyscore)1710 PHP_METHOD(RedisCluster, zrevrangebyscore) {
1711     generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZREVRANGEBYSCORE",
1712         redis_zrangebyscore_cmd);
1713 }
1714 /* }}} */
1715 
1716 /* {{{ proto array RedisCluster::zrangebylex(string key, string min, string max,
1717  *                                           [offset, count]) */
PHP_METHOD(RedisCluster,zrangebylex)1718 PHP_METHOD(RedisCluster, zrangebylex) {
1719     CLUSTER_PROCESS_KW_CMD("ZRANGEBYLEX", redis_zrangebylex_cmd,
1720         cluster_mbulk_resp, 1);
1721 }
1722 /* }}} */
1723 
1724 /* {{{ proto array RedisCluster::zrevrangebylex(string key, string min,
1725  *                                              string min, [long off, long limit) */
PHP_METHOD(RedisCluster,zrevrangebylex)1726 PHP_METHOD(RedisCluster, zrevrangebylex) {
1727     CLUSTER_PROCESS_KW_CMD("ZREVRANGEBYLEX", redis_zrangebylex_cmd,
1728         cluster_mbulk_resp, 1);
1729 }
1730 /* }}} */
1731 
1732 /* {{{ proto long RedisCluster::zlexcount(string key, string min, string max) */
PHP_METHOD(RedisCluster,zlexcount)1733 PHP_METHOD(RedisCluster, zlexcount) {
1734     CLUSTER_PROCESS_KW_CMD("ZLEXCOUNT", redis_gen_zlex_cmd, cluster_long_resp, 1);
1735 }
1736 /* }}} */
1737 
1738 /* {{{ proto long RedisCluster::zremrangebylex(string key, string min, string max) */
PHP_METHOD(RedisCluster,zremrangebylex)1739 PHP_METHOD(RedisCluster, zremrangebylex) {
1740     CLUSTER_PROCESS_KW_CMD("ZREMRANGEBYLEX", redis_gen_zlex_cmd,
1741         cluster_long_resp, 0);
1742 }
1743 /* }}} */
1744 
1745 /* {{{ proto array RedisCluster::zpopmax(string key) */
PHP_METHOD(RedisCluster,zpopmax)1746 PHP_METHOD(RedisCluster, zpopmax) {
1747     if (ZEND_NUM_ARGS() == 1) {
1748         CLUSTER_PROCESS_KW_CMD("ZPOPMAX", redis_key_cmd, cluster_mbulk_zipdbl_resp, 0);
1749     } else if (ZEND_NUM_ARGS() == 2) {
1750         CLUSTER_PROCESS_KW_CMD("ZPOPMAX", redis_key_long_cmd, cluster_mbulk_zipdbl_resp, 0);
1751     } else {
1752         ZEND_WRONG_PARAM_COUNT();
1753     }
1754 }
1755 /* }}} */
1756 
1757 /* {{{ proto array RedisCluster::zpopmin(string key) */
PHP_METHOD(RedisCluster,zpopmin)1758 PHP_METHOD(RedisCluster, zpopmin) {
1759     if (ZEND_NUM_ARGS() == 1) {
1760         CLUSTER_PROCESS_KW_CMD("ZPOPMIN", redis_key_cmd, cluster_mbulk_zipdbl_resp, 0);
1761     } else if (ZEND_NUM_ARGS() == 2) {
1762         CLUSTER_PROCESS_KW_CMD("ZPOPMIN", redis_key_long_cmd, cluster_mbulk_zipdbl_resp, 0);
1763     } else {
1764         ZEND_WRONG_PARAM_COUNT();
1765     }
1766 }
1767 /* }}} */
1768 
1769 /* {{{ proto array RedisCluster::bzPopMin(Array keys [, timeout]) }}} */
PHP_METHOD(RedisCluster,bzpopmax)1770 PHP_METHOD(RedisCluster, bzpopmax) {
1771     CLUSTER_PROCESS_KW_CMD("BZPOPMAX", redis_blocking_pop_cmd, cluster_mbulk_resp, 0);
1772 }
1773 
1774 /* {{{ proto array RedisCluster::bzPopMax(Array keys [, timeout]) }}} */
PHP_METHOD(RedisCluster,bzpopmin)1775 PHP_METHOD(RedisCluster, bzpopmin) {
1776     CLUSTER_PROCESS_KW_CMD("BZPOPMIN", redis_blocking_pop_cmd, cluster_mbulk_resp, 0);
1777 }
1778 
1779 /* {{{ proto RedisCluster::sort(string key, array options) */
PHP_METHOD(RedisCluster,sort)1780 PHP_METHOD(RedisCluster, sort) {
1781     redisCluster *c = GET_CONTEXT();
1782     char *cmd; int cmd_len, have_store; short slot;
1783 
1784     if (redis_sort_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, &have_store,
1785                       &cmd, &cmd_len, &slot, NULL) == FAILURE)
1786     {
1787         RETURN_FALSE;
1788     }
1789 
1790     if (cluster_send_command(c,slot,cmd,cmd_len) < 0 || c->err != NULL) {
1791         efree(cmd);
1792         RETURN_FALSE;
1793     }
1794 
1795     efree(cmd);
1796 
1797     // Response type differs based on presence of STORE argument
1798     if (!have_store) {
1799         cluster_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
1800     } else {
1801         cluster_long_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
1802     }
1803 }
1804 
1805 /* {{{ proto RedisCluster::object(string subcmd, string key) */
PHP_METHOD(RedisCluster,object)1806 PHP_METHOD(RedisCluster, object) {
1807     redisCluster *c = GET_CONTEXT();
1808     char *cmd; int cmd_len; short slot;
1809     REDIS_REPLY_TYPE rtype;
1810 
1811     if (redis_object_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, &rtype,
1812                         &cmd, &cmd_len, &slot, NULL) == FAILURE)
1813     {
1814         RETURN_FALSE;
1815     }
1816 
1817      if (cluster_send_command(c,slot,cmd,cmd_len) < 0 || c->err != NULL) {
1818         efree(cmd);
1819         RETURN_FALSE;
1820     }
1821 
1822     efree(cmd);
1823 
1824     // Use the correct response type
1825     if (rtype == TYPE_INT) {
1826         cluster_long_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
1827     } else {
1828         cluster_bulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
1829     }
1830 }
1831 
1832 /* {{{ proto null RedisCluster::subscribe(array chans, callable cb) */
PHP_METHOD(RedisCluster,subscribe)1833 PHP_METHOD(RedisCluster, subscribe) {
1834     CLUSTER_PROCESS_KW_CMD("SUBSCRIBE", redis_subscribe_cmd, cluster_sub_resp, 0);
1835 }
1836 /* }}} */
1837 
1838 /* {{{ proto null RedisCluster::psubscribe(array pats, callable cb) */
PHP_METHOD(RedisCluster,psubscribe)1839 PHP_METHOD(RedisCluster, psubscribe) {
1840     CLUSTER_PROCESS_KW_CMD("PSUBSCRIBE", redis_subscribe_cmd, cluster_sub_resp, 0);
1841 }
1842 /* }}} */
1843 
generic_unsub_cmd(INTERNAL_FUNCTION_PARAMETERS,redisCluster * c,char * kw)1844 static void generic_unsub_cmd(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
1845                               char *kw)
1846 {
1847     char *cmd;
1848     int cmd_len;
1849     void *ctx;
1850     short slot;
1851 
1852     // There is not reason to unsubscribe outside of a subscribe loop
1853     if (c->subscribed_slot == -1) {
1854         php_error_docref(0, E_WARNING,
1855             "You can't unsubscribe outside of a subscribe loop");
1856         RETURN_FALSE;
1857     }
1858 
1859     // Call directly because we're going to set the slot manually
1860     if (redis_unsubscribe_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, kw,
1861                              &cmd, &cmd_len, &slot, &ctx)
1862                              == FAILURE)
1863     {
1864         RETURN_FALSE;
1865     }
1866 
1867     // This has to operate on our subscribe slot
1868     if (cluster_send_slot(c, c->subscribed_slot, cmd, cmd_len, TYPE_MULTIBULK
1869                         ) == FAILURE)
1870     {
1871         CLUSTER_THROW_EXCEPTION("Failed to UNSUBSCRIBE within our subscribe loop!", 0);
1872         RETURN_FALSE;
1873     }
1874 
1875     // Now process response from the slot we're subscribed on
1876     cluster_unsub_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, ctx);
1877 
1878     // Cleanup our command
1879     efree(cmd);
1880 }
1881 
1882 /* {{{ proto array RedisCluster::unsubscribe(array chans) */
PHP_METHOD(RedisCluster,unsubscribe)1883 PHP_METHOD(RedisCluster, unsubscribe) {
1884     generic_unsub_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, GET_CONTEXT(),
1885         "UNSUBSCRIBE");
1886 }
1887 /* }}} */
1888 
1889 /* {{{ proto array RedisCluster::punsubscribe(array pats) */
PHP_METHOD(RedisCluster,punsubscribe)1890 PHP_METHOD(RedisCluster, punsubscribe) {
1891     generic_unsub_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, GET_CONTEXT(),
1892         "PUNSUBSCRIBE");
1893 }
1894 /* }}} */
1895 
1896 /* {{{ proto mixed RedisCluster::eval(string script, [array args, int numkeys) */
PHP_METHOD(RedisCluster,eval)1897 PHP_METHOD(RedisCluster, eval) {
1898     CLUSTER_PROCESS_KW_CMD("EVAL", redis_eval_cmd, cluster_variant_raw_resp, 0);
1899 }
1900 /* }}} */
1901 
1902 /* {{{ proto mixed RedisCluster::evalsha(string sha, [array args, int numkeys]) */
PHP_METHOD(RedisCluster,evalsha)1903 PHP_METHOD(RedisCluster, evalsha) {
1904     CLUSTER_PROCESS_KW_CMD("EVALSHA", redis_eval_cmd, cluster_variant_raw_resp, 0);
1905 }
1906 /* }}} */
1907 
1908 /* Commands that do not interact with Redis, but just report stuff about
1909  * various options, etc */
1910 
1911 /* {{{ proto string RedisCluster::getmode() */
PHP_METHOD(RedisCluster,getmode)1912 PHP_METHOD(RedisCluster, getmode) {
1913     redisCluster *c = GET_CONTEXT();
1914     RETURN_LONG(c->flags->mode);
1915 }
1916 /* }}} */
1917 
1918 /* {{{ proto string RedisCluster::getlasterror() */
PHP_METHOD(RedisCluster,getlasterror)1919 PHP_METHOD(RedisCluster, getlasterror) {
1920     redisCluster *c = GET_CONTEXT();
1921 
1922     if (c->err) {
1923         RETURN_STRINGL(ZSTR_VAL(c->err), ZSTR_LEN(c->err));
1924     }
1925     RETURN_NULL();
1926 }
1927 /* }}} */
1928 
1929 /* {{{ proto bool RedisCluster::clearlasterror() */
PHP_METHOD(RedisCluster,clearlasterror)1930 PHP_METHOD(RedisCluster, clearlasterror) {
1931     redisCluster *c = GET_CONTEXT();
1932 
1933     if (c->err) {
1934         zend_string_release(c->err);
1935         c->err = NULL;
1936     }
1937 
1938     RETURN_TRUE;
1939 }
1940 /* }}} */
1941 
1942 /* {{{ proto long RedisCluster::getOption(long option */
PHP_METHOD(RedisCluster,getoption)1943 PHP_METHOD(RedisCluster, getoption) {
1944     redisCluster *c = GET_CONTEXT();
1945     redis_getoption_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, c);
1946 }
1947 /* }}} */
1948 
1949 /* {{{ proto bool RedisCluster::setOption(long option, mixed value) */
PHP_METHOD(RedisCluster,setoption)1950 PHP_METHOD(RedisCluster, setoption) {
1951     redisCluster *c = GET_CONTEXT();
1952     redis_setoption_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, c);
1953 }
1954 /* }}} */
1955 
1956 /* {{{ proto string RedisCluster::_prefix(string key) */
PHP_METHOD(RedisCluster,_prefix)1957 PHP_METHOD(RedisCluster, _prefix) {
1958     redisCluster *c = GET_CONTEXT();
1959     redis_prefix_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags);
1960 }
1961 /* }}} */
1962 
1963 /* {{{ proto string RedisCluster::_serialize(mixed val) */
PHP_METHOD(RedisCluster,_serialize)1964 PHP_METHOD(RedisCluster, _serialize) {
1965     redisCluster *c = GET_CONTEXT();
1966     redis_serialize_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags);
1967 }
1968 /* }}} */
1969 
1970 /* {{{ proto mixed RedisCluster::_unserialize(string val) */
PHP_METHOD(RedisCluster,_unserialize)1971 PHP_METHOD(RedisCluster, _unserialize) {
1972     redisCluster *c = GET_CONTEXT();
1973     redis_unserialize_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU,
1974         c->flags, redis_cluster_exception_ce);
1975 }
1976 /* }}} */
1977 
PHP_METHOD(RedisCluster,_compress)1978 PHP_METHOD(RedisCluster, _compress) {
1979     redisCluster *c = GET_CONTEXT();
1980     redis_compress_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags);
1981 }
1982 
PHP_METHOD(RedisCluster,_uncompress)1983 PHP_METHOD(RedisCluster, _uncompress) {
1984     redisCluster *c = GET_CONTEXT();
1985     redis_uncompress_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags,
1986                              redis_cluster_exception_ce);
1987 }
1988 
PHP_METHOD(RedisCluster,_pack)1989 PHP_METHOD(RedisCluster, _pack) {
1990     redisCluster *c = GET_CONTEXT();
1991     redis_pack_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags);
1992 }
1993 
PHP_METHOD(RedisCluster,_unpack)1994 PHP_METHOD(RedisCluster, _unpack) {
1995     redisCluster *c = GET_CONTEXT();
1996     redis_unpack_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags);
1997 }
1998 
1999 /* {{{ proto array RedisCluster::_masters() */
PHP_METHOD(RedisCluster,_masters)2000 PHP_METHOD(RedisCluster, _masters) {
2001     redisCluster *c = GET_CONTEXT();
2002     redisClusterNode *node;
2003 
2004     array_init(return_value);
2005 
2006     ZEND_HASH_FOREACH_PTR(c->nodes, node) {
2007         if (node == NULL) break;
2008 
2009         zval z_sub;
2010 
2011         array_init(&z_sub);
2012 
2013         add_next_index_stringl(&z_sub, ZSTR_VAL(node->sock->host), ZSTR_LEN(node->sock->host));
2014         add_next_index_long(&z_sub, node->sock->port);
2015         add_next_index_zval(return_value, &z_sub);
2016     } ZEND_HASH_FOREACH_END();
2017 }
2018 
PHP_METHOD(RedisCluster,_redir)2019 PHP_METHOD(RedisCluster, _redir) {
2020     redisCluster *c = GET_CONTEXT();
2021     char buf[255];
2022     size_t len;
2023 
2024     len = snprintf(buf, sizeof(buf), "%s:%d", c->redir_host, c->redir_port);
2025     if (*c->redir_host && c->redir_host_len) {
2026         RETURN_STRINGL(buf, len);
2027     } else {
2028         RETURN_NULL();
2029     }
2030 }
2031 
2032 /*
2033  * Transaction handling
2034  */
2035 
2036 /* {{{ proto bool RedisCluster::multi() */
PHP_METHOD(RedisCluster,multi)2037 PHP_METHOD(RedisCluster, multi) {
2038     redisCluster *c = GET_CONTEXT();
2039 
2040     if (c->flags->mode == MULTI) {
2041         php_error_docref(NULL, E_WARNING,
2042             "RedisCluster is already in MULTI mode, ignoring");
2043         RETURN_FALSE;
2044     }
2045 
2046     /* Flag that we're in MULTI mode */
2047     c->flags->mode = MULTI;
2048 
2049     /* Return our object so we can chain MULTI calls */
2050     RETVAL_ZVAL(getThis(), 1, 0);
2051 }
2052 
2053 /* {{{ proto bool RedisCluster::watch() */
PHP_METHOD(RedisCluster,watch)2054 PHP_METHOD(RedisCluster, watch) {
2055     redisCluster *c = GET_CONTEXT();
2056     HashTable *ht_dist;
2057     clusterDistList *dl;
2058     smart_string cmd = {0};
2059     zval *z_args;
2060     int argc = ZEND_NUM_ARGS(), i;
2061     zend_ulong slot;
2062     zend_string *zstr;
2063 
2064     // Disallow in MULTI mode
2065     if (c->flags->mode == MULTI) {
2066         php_error_docref(NULL, E_WARNING,
2067             "WATCH command not allowed in MULTI mode");
2068         RETURN_FALSE;
2069     }
2070 
2071     // Don't need to process zero arguments
2072     if (!argc) RETURN_FALSE;
2073 
2074     // Create our distribution HashTable
2075     ht_dist = cluster_dist_create();
2076 
2077     // Allocate args, and grab them
2078     z_args = emalloc(sizeof(zval) * argc);
2079     if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
2080         efree(z_args);
2081         cluster_dist_free(ht_dist);
2082         RETURN_FALSE;
2083     }
2084 
2085     // Loop through arguments, prefixing if needed
2086     for(i = 0 ; i < argc; i++) {
2087         // We'll need the key as a string
2088         zstr = zval_get_string(&z_args[i]);
2089 
2090         // Add this key to our distribution handler
2091         if (cluster_dist_add_key(c, ht_dist, ZSTR_VAL(zstr), ZSTR_LEN(zstr), NULL) == FAILURE) {
2092             CLUSTER_THROW_EXCEPTION("Can't issue WATCH command as the keyspace isn't fully mapped", 0);
2093             zend_string_release(zstr);
2094             RETURN_FALSE;
2095         }
2096         zend_string_release(zstr);
2097     }
2098 
2099     // Iterate over each node we'll be sending commands to
2100     ZEND_HASH_FOREACH_PTR(ht_dist, dl) {
2101         // Grab the clusterDistList pointer itself
2102         if (dl == NULL) {
2103             CLUSTER_THROW_EXCEPTION("Internal error in a PHP HashTable", 0);
2104             cluster_dist_free(ht_dist);
2105             efree(z_args);
2106             efree(cmd.c);
2107             RETURN_FALSE;
2108         } else if (zend_hash_get_current_key(ht_dist, NULL, &slot) != HASH_KEY_IS_LONG) {
2109             break;
2110         }
2111 
2112         // Construct our watch command for this node
2113         redis_cmd_init_sstr(&cmd, dl->len, "WATCH", sizeof("WATCH")-1);
2114         for (i = 0; i < dl->len; i++) {
2115             redis_cmd_append_sstr(&cmd, dl->entry[i].key,
2116                 dl->entry[i].key_len);
2117         }
2118 
2119         // If we get a failure from this, we have to abort
2120         if (cluster_send_command(c,(short)slot,cmd.c,cmd.len) ==-1) {
2121             RETURN_FALSE;
2122         }
2123 
2124         // This node is watching
2125         SLOT_SOCK(c, (short)slot)->watching = 1;
2126 
2127         // Zero out our command buffer
2128         cmd.len = 0;
2129     } ZEND_HASH_FOREACH_END();
2130 
2131     // Cleanup
2132     cluster_dist_free(ht_dist);
2133     efree(z_args);
2134     efree(cmd.c);
2135 
2136     RETURN_TRUE;
2137 }
2138 
2139 /* {{{ proto bool RedisCluster::unwatch() */
PHP_METHOD(RedisCluster,unwatch)2140 PHP_METHOD(RedisCluster, unwatch) {
2141     redisCluster *c = GET_CONTEXT();
2142     short slot;
2143 
2144     // Send UNWATCH to nodes that need it
2145     for(slot = 0; slot < REDIS_CLUSTER_SLOTS; slot++) {
2146         if (c->master[slot] && SLOT_SOCK(c,slot)->watching) {
2147             if (cluster_send_slot(c, slot, RESP_UNWATCH_CMD,
2148                                  sizeof(RESP_UNWATCH_CMD)-1,
2149                                  TYPE_LINE) ==-1)
2150             {
2151                 CLUSTER_RETURN_BOOL(c, 0);
2152             }
2153 
2154             // No longer watching
2155             SLOT_SOCK(c,slot)->watching = 0;
2156         }
2157     }
2158 
2159     CLUSTER_RETURN_BOOL(c, 1);
2160 }
2161 
2162 /* {{{ proto array RedisCluster::exec() */
PHP_METHOD(RedisCluster,exec)2163 PHP_METHOD(RedisCluster, exec) {
2164     redisCluster *c = GET_CONTEXT();
2165     clusterFoldItem *fi;
2166 
2167     // Verify we are in fact in multi mode
2168     if (CLUSTER_IS_ATOMIC(c)) {
2169         php_error_docref(NULL, E_WARNING, "RedisCluster is not in MULTI mode");
2170         RETURN_FALSE;
2171     }
2172 
2173     // First pass, send EXEC and abort on failure
2174     fi = c->multi_head;
2175     while (fi) {
2176         if (SLOT_SOCK(c, fi->slot)->mode == MULTI) {
2177             if ( cluster_send_exec(c, fi->slot) < 0) {
2178                 cluster_abort_exec(c);
2179                 CLUSTER_THROW_EXCEPTION("Error processing EXEC across the cluster", 0);
2180 
2181                 // Free our queue, reset MULTI state
2182                 CLUSTER_FREE_QUEUE(c);
2183                 CLUSTER_RESET_MULTI(c);
2184 
2185                 RETURN_FALSE;
2186             }
2187             SLOT_SOCK(c, fi->slot)->mode     = ATOMIC;
2188             SLOT_SOCK(c, fi->slot)->watching = 0;
2189         }
2190         fi = fi->next;
2191     }
2192 
2193     // MULTI multi-bulk response handler
2194     cluster_multi_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
2195 
2196     // Free our callback queue, any enqueued distributed command context items
2197     // and reset our MULTI state.
2198     CLUSTER_FREE_QUEUE(c);
2199     CLUSTER_RESET_MULTI(c);
2200 }
2201 
2202 /* {{{ proto bool RedisCluster::discard() */
PHP_METHOD(RedisCluster,discard)2203 PHP_METHOD(RedisCluster, discard) {
2204     redisCluster *c = GET_CONTEXT();
2205 
2206     if (CLUSTER_IS_ATOMIC(c)) {
2207         php_error_docref(NULL, E_WARNING, "Cluster is not in MULTI mode");
2208         RETURN_FALSE;
2209     }
2210 
2211     if (cluster_abort_exec(c) < 0) {
2212         CLUSTER_RESET_MULTI(c);
2213     }
2214 
2215     CLUSTER_FREE_QUEUE(c);
2216 
2217     RETURN_TRUE;
2218 }
2219 
2220 /* Get a slot either by key (string) or host/port array */
2221 static short
cluster_cmd_get_slot(redisCluster * c,zval * z_arg)2222 cluster_cmd_get_slot(redisCluster *c, zval *z_arg)
2223 {
2224     size_t key_len;
2225     int key_free;
2226     zval *z_host, *z_port;
2227     short slot;
2228     char *key;
2229     zend_string *zstr;
2230 
2231     /* If it's a string, treat it as a key.  Otherwise, look for a two
2232      * element array */
2233     if (Z_TYPE_P(z_arg) ==IS_STRING || Z_TYPE_P(z_arg) ==IS_LONG ||
2234        Z_TYPE_P(z_arg) ==IS_DOUBLE)
2235     {
2236         /* Allow for any scalar here */
2237         zstr = zval_get_string(z_arg);
2238         key = ZSTR_VAL(zstr);
2239         key_len = ZSTR_LEN(zstr);
2240 
2241         /* Hash it */
2242         key_free = redis_key_prefix(c->flags, &key, &key_len);
2243         slot = cluster_hash_key(key, key_len);
2244         zend_string_release(zstr);
2245         if (key_free) efree(key);
2246     } else if (Z_TYPE_P(z_arg) == IS_ARRAY &&
2247         (z_host = zend_hash_index_find(Z_ARRVAL_P(z_arg), 0)) != NULL &&
2248         (z_port = zend_hash_index_find(Z_ARRVAL_P(z_arg), 1)) != NULL &&
2249         Z_TYPE_P(z_host) == IS_STRING && Z_TYPE_P(z_port) == IS_LONG
2250     ) {
2251         /* Attempt to find this specific node by host:port */
2252         slot = cluster_find_slot(c,(const char *)Z_STRVAL_P(z_host),
2253             (unsigned short)Z_LVAL_P(z_port));
2254 
2255         /* Inform the caller if they've passed bad data */
2256         if (slot < 0) {
2257             php_error_docref(0, E_WARNING, "Unknown node %s:" ZEND_LONG_FMT,
2258                 Z_STRVAL_P(z_host), Z_LVAL_P(z_port));
2259         }
2260     } else {
2261         php_error_docref(0, E_WARNING,
2262             "Directed commands must be passed a key or [host,port] array");
2263         return -1;
2264     }
2265 
2266     return slot;
2267 }
2268 
2269 /* Generic handler for things we want directed at a given node, like SAVE,
2270  * BGSAVE, FLUSHDB, FLUSHALL, etc */
2271 static void
cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAMETERS,char * kw,REDIS_REPLY_TYPE reply_type,cluster_cb cb)2272 cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw,
2273                        REDIS_REPLY_TYPE reply_type, cluster_cb cb)
2274 {
2275     redisCluster *c = GET_CONTEXT();
2276     char *cmd;
2277     int cmd_len;
2278     zval *z_arg;
2279     short slot;
2280 
2281     if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &z_arg) == FAILURE) {
2282         RETURN_FALSE;
2283     }
2284 
2285     // One argument means find the node (treated like a key), and two means
2286     // send the command to a specific host and port
2287     slot = cluster_cmd_get_slot(c, z_arg);
2288     if (slot < 0) {
2289         RETURN_FALSE;
2290     }
2291 
2292     // Construct our command
2293     cmd_len = redis_spprintf(NULL, NULL, &cmd, kw, "");
2294 
2295     // Kick off our command
2296     if (cluster_send_slot(c, slot, cmd, cmd_len, reply_type) < 0) {
2297         CLUSTER_THROW_EXCEPTION("Unable to send command at a specific node", 0);
2298         efree(cmd);
2299         RETURN_FALSE;
2300     }
2301 
2302     // Our response callback
2303     cb(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
2304 
2305     // Free our command
2306     efree(cmd);
2307 }
2308 
2309 static void
cluster_flush_cmd(INTERNAL_FUNCTION_PARAMETERS,char * kw,REDIS_REPLY_TYPE reply_type,cluster_cb cb)2310 cluster_flush_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw, REDIS_REPLY_TYPE reply_type, cluster_cb cb)
2311 {
2312     redisCluster *c = GET_CONTEXT();
2313     char *cmd;
2314     int cmd_len;
2315     zval *z_arg;
2316     zend_bool async = 0;
2317     short slot;
2318 
2319     if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|b", &z_arg, &async) == FAILURE) {
2320         RETURN_FALSE;
2321     }
2322 
2323     // One argument means find the node (treated like a key), and two means
2324     // send the command to a specific host and port
2325     slot = cluster_cmd_get_slot(c, z_arg);
2326     if (slot < 0) {
2327         RETURN_FALSE;
2328     }
2329 
2330     // Construct our command
2331     if (async) {
2332         cmd_len = redis_spprintf(NULL, NULL, &cmd, kw, "s", "ASYNC", sizeof("ASYNC") - 1);
2333     } else {
2334         cmd_len = redis_spprintf(NULL, NULL, &cmd, kw, "");
2335     }
2336 
2337 
2338     // Kick off our command
2339     if (cluster_send_slot(c, slot, cmd, cmd_len, reply_type) < 0) {
2340         CLUSTER_THROW_EXCEPTION("Unable to send command at a specific node", 0);
2341         efree(cmd);
2342         RETURN_FALSE;
2343     }
2344 
2345     // Our response callback
2346     cb(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
2347 
2348     // Free our command
2349     efree(cmd);
2350 }
2351 
2352 /* Generic routine for handling various commands which need to be directed at
2353  * a node, but have complex syntax.  We simply parse out the arguments and send
2354  * the command as constructed by the caller */
cluster_raw_cmd(INTERNAL_FUNCTION_PARAMETERS,char * kw,int kw_len)2355 static void cluster_raw_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len)
2356 {
2357     redisCluster *c = GET_CONTEXT();
2358     smart_string cmd = {0};
2359     zval *z_args;
2360     short slot;
2361     int i, argc = ZEND_NUM_ARGS();
2362 
2363     /* Commands using this pass-thru don't need to be enabled in MULTI mode */
2364     if (!CLUSTER_IS_ATOMIC(c)) {
2365         php_error_docref(0, E_WARNING,
2366             "Command can't be issued in MULTI mode");
2367         RETURN_FALSE;
2368     }
2369 
2370     /* We at least need the key or [host,port] argument */
2371     if (argc < 1) {
2372         php_error_docref(0, E_WARNING,
2373             "Command requires at least an argument to direct to a node");
2374         RETURN_FALSE;
2375     }
2376 
2377     /* Allocate an array to process arguments */
2378     z_args = emalloc(argc * sizeof(zval));
2379 
2380     /* Grab args */
2381     if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
2382         efree(z_args);
2383         RETURN_FALSE;
2384     }
2385 
2386     /* First argument needs to be the "where" */
2387     if ((slot = cluster_cmd_get_slot(c, &z_args[0])) < 0) {
2388         efree(z_args);
2389         RETURN_FALSE;
2390     }
2391 
2392     /* Initialize our command */
2393     redis_cmd_init_sstr(&cmd, argc-1, kw, kw_len);
2394 
2395     /* Iterate, appending args */
2396     for(i = 1; i < argc; i++) {
2397         zend_string *zstr = zval_get_string(&z_args[i]);
2398         redis_cmd_append_sstr(&cmd, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
2399         zend_string_release(zstr);
2400     }
2401 
2402     /* Send it off */
2403     if (cluster_send_slot(c, slot, cmd.c, cmd.len, TYPE_EOF) < 0) {
2404         CLUSTER_THROW_EXCEPTION("Couldn't send command to node", 0);
2405         efree(cmd.c);
2406         efree(z_args);
2407         RETURN_FALSE;
2408     }
2409 
2410     /* Read the response variant */
2411     cluster_variant_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
2412 
2413     efree(cmd.c);
2414     efree(z_args);
2415 }
2416 
2417 /* Generic method for HSCAN, SSCAN, and ZSCAN */
cluster_kscan_cmd(INTERNAL_FUNCTION_PARAMETERS,REDIS_SCAN_TYPE type)2418 static void cluster_kscan_cmd(INTERNAL_FUNCTION_PARAMETERS,
2419                               REDIS_SCAN_TYPE type)
2420 {
2421     redisCluster *c = GET_CONTEXT();
2422     char *cmd, *pat = NULL, *key = NULL;
2423     size_t key_len = 0, pat_len = 0, pat_free = 0;
2424     int cmd_len, key_free = 0;
2425     short slot;
2426     zval *z_it;
2427     HashTable *hash;
2428     long it, num_ele;
2429     zend_long count = 0;
2430 
2431     // Can't be in MULTI mode
2432     if (!CLUSTER_IS_ATOMIC(c)) {
2433         CLUSTER_THROW_EXCEPTION("SCAN type commands can't be called in MULTI mode!", 0);
2434         RETURN_FALSE;
2435     }
2436 
2437     /* Parse arguments */
2438     if (zend_parse_parameters(ZEND_NUM_ARGS(), "sz/|s!l", &key,
2439                              &key_len, &z_it, &pat, &pat_len, &count) == FAILURE)
2440     {
2441         RETURN_FALSE;
2442     }
2443 
2444     /* Treat as readonly */
2445     c->readonly = 1;
2446 
2447     // Convert iterator to long if it isn't, update our long iterator if it's
2448     // set and >0, and finish if it's back to zero
2449     if (Z_TYPE_P(z_it) != IS_LONG || Z_LVAL_P(z_it) < 0) {
2450         convert_to_long(z_it);
2451         it = 0;
2452     } else if (Z_LVAL_P(z_it) != 0) {
2453         it = Z_LVAL_P(z_it);
2454     } else {
2455         RETURN_FALSE;
2456     }
2457 
2458     // Apply any key prefix we have, get the slot
2459     key_free = redis_key_prefix(c->flags, &key, &key_len);
2460     slot = cluster_hash_key(key, key_len);
2461 
2462     if (c->flags->scan & REDIS_SCAN_PREFIX) {
2463         pat_free = redis_key_prefix(c->flags, &pat, &pat_len);
2464     }
2465 
2466     // If SCAN_RETRY is set, loop until we get a zero iterator or until
2467     // we get non-zero elements.  Otherwise we just send the command once.
2468     do {
2469         /* Free our return value if we're back in the loop */
2470         if (Z_TYPE_P(return_value) == IS_ARRAY) {
2471             zval_dtor(return_value);
2472             ZVAL_NULL(return_value);
2473         }
2474 
2475         // Create command
2476         cmd_len = redis_fmt_scan_cmd(&cmd, type, key, key_len, it, pat, pat_len,
2477             count);
2478 
2479         // Send it off
2480         if (cluster_send_command(c, slot, cmd, cmd_len) == FAILURE)
2481         {
2482             CLUSTER_THROW_EXCEPTION("Couldn't send SCAN command", 0);
2483             if (key_free) efree(key);
2484             efree(cmd);
2485             RETURN_FALSE;
2486         }
2487 
2488         // Read response
2489         if (cluster_scan_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, type,
2490                               &it) == FAILURE)
2491         {
2492             CLUSTER_THROW_EXCEPTION("Couldn't read SCAN response", 0);
2493             if (key_free) efree(key);
2494             efree(cmd);
2495             RETURN_FALSE;
2496         }
2497 
2498         // Count the elements we got back
2499         hash = Z_ARRVAL_P(return_value);
2500         num_ele = zend_hash_num_elements(hash);
2501 
2502         // Free our command
2503         efree(cmd);
2504     } while (c->flags->scan & REDIS_SCAN_RETRY && it != 0 && num_ele == 0);
2505 
2506     // Free our pattern
2507     if (pat_free) efree(pat);
2508 
2509     // Free our key
2510     if (key_free) efree(key);
2511 
2512     // Update iterator reference
2513     Z_LVAL_P(z_it) = it;
2514 }
2515 
redis_acl_op_readonly(zend_string * op)2516 static int redis_acl_op_readonly(zend_string *op) {
2517     /* Only return read-only for operations we know to be */
2518     if (ZSTR_STRICMP_STATIC(op, "LIST") ||
2519         ZSTR_STRICMP_STATIC(op, "USERS") ||
2520         ZSTR_STRICMP_STATIC(op, "GETUSER") ||
2521         ZSTR_STRICMP_STATIC(op, "CAT") ||
2522         ZSTR_STRICMP_STATIC(op, "GENPASS") ||
2523         ZSTR_STRICMP_STATIC(op, "WHOAMI") ||
2524         ZSTR_STRICMP_STATIC(op, "LOG")) return 1;
2525 
2526     return 0;
2527 }
2528 
PHP_METHOD(RedisCluster,acl)2529 PHP_METHOD(RedisCluster, acl) {
2530     redisCluster *c = GET_CONTEXT();
2531     smart_string cmdstr = {0};
2532     int argc = ZEND_NUM_ARGS(), i, readonly;
2533     cluster_cb cb;
2534     zend_string *zs;
2535     zval *zargs;
2536     void *ctx = NULL;
2537     short slot;
2538 
2539     /* ACL in cluster needs a slot argument, and then at least the op */
2540     if (argc < 2) {
2541         WRONG_PARAM_COUNT;
2542         RETURN_FALSE;
2543     }
2544 
2545     /* Grab all our arguments and determine the command slot */
2546     zargs = emalloc(argc * sizeof(*zargs));
2547     if (zend_get_parameters_array(ht, argc, zargs) == FAILURE ||
2548         (slot = cluster_cmd_get_slot(c, &zargs[0]) < 0))
2549     {
2550         efree(zargs);
2551         RETURN_FALSE;
2552     }
2553 
2554     REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc - 1, "ACL");
2555 
2556     /* Read the op, determin if it's readonly, and add it */
2557     zs = zval_get_string(&zargs[1]);
2558     readonly = redis_acl_op_readonly(zs);
2559     redis_cmd_append_sstr_zstr(&cmdstr, zs);
2560 
2561     /* We have specialized handlers for GETUSER and LOG, whereas every
2562      * other ACL command can be handled generically */
2563     if (zend_string_equals_literal_ci(zs, "GETUSER")) {
2564         cb = cluster_acl_getuser_resp;
2565     } else if (zend_string_equals_literal_ci(zs, "LOG")) {
2566         cb = cluster_acl_log_resp;
2567     } else {
2568         cb = cluster_variant_resp;
2569     }
2570 
2571     zend_string_release(zs);
2572 
2573     /* Process remaining args */
2574     for (i = 2; i < argc; i++) {
2575         zs = zval_get_string(&zargs[i]);
2576         redis_cmd_append_sstr_zstr(&cmdstr, zs);
2577         zend_string_release(zs);
2578     }
2579 
2580     /* Can we use replicas? */
2581     c->readonly = readonly && CLUSTER_IS_ATOMIC(c);
2582 
2583     /* Kick off our command */
2584     if (cluster_send_slot(c, slot, cmdstr.c, cmdstr.len, TYPE_EOF) < 0) {
2585         CLUSTER_THROW_EXCEPTION("Unabler to send ACL command", 0);
2586         efree(zargs);
2587         RETURN_FALSE;
2588     }
2589 
2590     if (CLUSTER_IS_ATOMIC(c)) {
2591         cb(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
2592     } else {
2593         CLUSTER_ENQUEUE_RESPONSE(c, slot, cb, ctx);
2594     }
2595 
2596     efree(cmdstr.c);
2597     efree(zargs);
2598 }
2599 
2600 /* {{{ proto RedisCluster::scan(string master, long it [, string pat, long cnt]) */
PHP_METHOD(RedisCluster,scan)2601 PHP_METHOD(RedisCluster, scan) {
2602     redisCluster *c = GET_CONTEXT();
2603     char *cmd, *pat = NULL;
2604     size_t pat_len = 0;
2605     int cmd_len;
2606     short slot;
2607     zval *z_it, *z_node;
2608     long it, num_ele, pat_free = 0;
2609     zend_long count = 0;
2610 
2611     /* Treat as read-only */
2612     c->readonly = CLUSTER_IS_ATOMIC(c);
2613 
2614     /* Can't be in MULTI mode */
2615     if (!CLUSTER_IS_ATOMIC(c)) {
2616         CLUSTER_THROW_EXCEPTION("SCAN type commands can't be called in MULTI mode", 0);
2617         RETURN_FALSE;
2618     }
2619 
2620     /* Parse arguments */
2621     if (zend_parse_parameters(ZEND_NUM_ARGS(), "z/z|s!l", &z_it,
2622                              &z_node, &pat, &pat_len, &count) == FAILURE)
2623     {
2624         RETURN_FALSE;
2625     }
2626 
2627     /* Convert or update iterator */
2628     if (Z_TYPE_P(z_it) != IS_LONG || Z_LVAL_P(z_it) < 0) {
2629         convert_to_long(z_it);
2630         it = 0;
2631     } else if (Z_LVAL_P(z_it) != 0) {
2632         it = Z_LVAL_P(z_it);
2633     } else {
2634         RETURN_FALSE;
2635     }
2636 
2637     if (c->flags->scan & REDIS_SCAN_PREFIX) {
2638         pat_free = redis_key_prefix(c->flags, &pat, &pat_len);
2639     }
2640 
2641     /* With SCAN_RETRY on, loop until we get some keys, otherwise just return
2642      * what Redis does, as it does */
2643     do {
2644         /* Free our return value if we're back in the loop */
2645         if (Z_TYPE_P(return_value) == IS_ARRAY) {
2646             zval_dtor(return_value);
2647             ZVAL_NULL(return_value);
2648         }
2649 
2650         /* Construct our command */
2651         cmd_len = redis_fmt_scan_cmd(&cmd, TYPE_SCAN, NULL, 0, it, pat, pat_len,
2652             count);
2653 
2654         if ((slot = cluster_cmd_get_slot(c, z_node)) < 0) {
2655            RETURN_FALSE;
2656         }
2657 
2658         // Send it to the node in question
2659         if (cluster_send_command(c, slot, cmd, cmd_len) < 0)
2660         {
2661             CLUSTER_THROW_EXCEPTION("Couldn't send SCAN to node", 0);
2662             efree(cmd);
2663             RETURN_FALSE;
2664         }
2665 
2666         if (cluster_scan_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, TYPE_SCAN,
2667                            &it) == FAILURE || Z_TYPE_P(return_value)!=IS_ARRAY)
2668         {
2669             CLUSTER_THROW_EXCEPTION("Couldn't process SCAN response from node", 0);
2670             efree(cmd);
2671             RETURN_FALSE;
2672         }
2673 
2674         efree(cmd);
2675 
2676         num_ele = zend_hash_num_elements(Z_ARRVAL_P(return_value));
2677     } while (c->flags->scan & REDIS_SCAN_RETRY && it != 0 && num_ele == 0);
2678 
2679     if (pat_free) efree(pat);
2680 
2681     Z_LVAL_P(z_it) = it;
2682 }
2683 /* }}} */
2684 
2685 /* {{{ proto RedisCluster::sscan(string key, long it [string pat, long cnt]) */
PHP_METHOD(RedisCluster,sscan)2686 PHP_METHOD(RedisCluster, sscan) {
2687     cluster_kscan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_SSCAN);
2688 }
2689 /* }}} */
2690 
2691 /* {{{ proto RedisCluster::zscan(string key, long it [string pat, long cnt]) */
PHP_METHOD(RedisCluster,zscan)2692 PHP_METHOD(RedisCluster, zscan) {
2693     cluster_kscan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_ZSCAN);
2694 }
2695 /* }}} */
2696 
2697 /* {{{ proto RedisCluster::hscan(string key, long it [string pat, long cnt]) */
PHP_METHOD(RedisCluster,hscan)2698 PHP_METHOD(RedisCluster, hscan) {
2699     cluster_kscan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_HSCAN);
2700 }
2701 /* }}} */
2702 
2703 /* {{{ proto RedisCluster::save(string key)
2704  *     proto RedisCluster::save(array host_port) */
PHP_METHOD(RedisCluster,save)2705 PHP_METHOD(RedisCluster, save) {
2706     cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SAVE", TYPE_LINE,
2707         cluster_bool_resp);
2708 }
2709 /* }}} */
2710 
2711 /* {{{ proto RedisCluster::bgsave(string key)
2712  *     proto RedisCluster::bgsave(array host_port) */
PHP_METHOD(RedisCluster,bgsave)2713 PHP_METHOD(RedisCluster, bgsave) {
2714     cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "BGSAVE",
2715         TYPE_LINE, cluster_bool_resp);
2716 }
2717 /* }}} */
2718 
2719 /* {{{ proto RedisCluster::flushdb(string key, [bool async])
2720  *     proto RedisCluster::flushdb(array host_port, [bool async]) */
PHP_METHOD(RedisCluster,flushdb)2721 PHP_METHOD(RedisCluster, flushdb) {
2722     cluster_flush_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "FLUSHDB",
2723         TYPE_LINE, cluster_bool_resp);
2724 }
2725 /* }}} */
2726 
2727 /* {{{ proto RedisCluster::flushall(string key, [bool async])
2728  *     proto RedisCluster::flushall(array host_port, [bool async]) */
PHP_METHOD(RedisCluster,flushall)2729 PHP_METHOD(RedisCluster, flushall) {
2730     cluster_flush_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "FLUSHALL",
2731         TYPE_LINE, cluster_bool_resp);
2732 }
2733 /* }}} */
2734 
2735 /* {{{ proto RedisCluster::dbsize(string key)
2736  *     proto RedisCluster::dbsize(array host_port) */
PHP_METHOD(RedisCluster,dbsize)2737 PHP_METHOD(RedisCluster, dbsize) {
2738     cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DBSIZE",
2739         TYPE_INT, cluster_long_resp);
2740 }
2741 /* }}} */
2742 
2743 /* {{{ proto RedisCluster::bgrewriteaof(string key)
2744  *     proto RedisCluster::bgrewriteaof(array host_port) */
PHP_METHOD(RedisCluster,bgrewriteaof)2745 PHP_METHOD(RedisCluster, bgrewriteaof) {
2746     cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "BGREWRITEAOF",
2747         TYPE_LINE, cluster_bool_resp);
2748 }
2749 /* }}} */
2750 
2751 /* {{{ proto RedisCluster::lastsave(string key)
2752  *     proto RedisCluster::lastsave(array $host_port) */
PHP_METHOD(RedisCluster,lastsave)2753 PHP_METHOD(RedisCluster, lastsave) {
2754     cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "LASTSAVE",
2755         TYPE_INT, cluster_long_resp);
2756 }
2757 /* }}} */
2758 
2759 /* {{{ proto array RedisCluster::info(string key, [string $arg])
2760  *     proto array RedisCluster::info(array host_port, [string $arg]) */
PHP_METHOD(RedisCluster,info)2761 PHP_METHOD(RedisCluster, info) {
2762     redisCluster *c = GET_CONTEXT();
2763     REDIS_REPLY_TYPE rtype;
2764     char *cmd, *opt = NULL;
2765     int cmd_len;
2766     size_t opt_len = 0;
2767     void *ctx = NULL;
2768 
2769     zval *z_arg;
2770     short slot;
2771 
2772     if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|s", &z_arg, &opt,
2773                              &opt_len) == FAILURE)
2774     {
2775         RETURN_FALSE;
2776     }
2777 
2778     /* Treat INFO as non read-only, as we probably want the master */
2779     c->readonly = 0;
2780 
2781     slot = cluster_cmd_get_slot(c, z_arg);
2782     if (slot < 0) {
2783         RETURN_FALSE;
2784     }
2785 
2786     if (opt != NULL) {
2787         cmd_len = redis_spprintf(NULL, NULL, &cmd, "INFO", "s", opt, opt_len);
2788     } else {
2789         cmd_len = redis_spprintf(NULL, NULL, &cmd, "INFO", "");
2790     }
2791 
2792     rtype = CLUSTER_IS_ATOMIC(c) ? TYPE_BULK : TYPE_LINE;
2793     if (cluster_send_slot(c, slot, cmd, cmd_len, rtype) < 0) {
2794         CLUSTER_THROW_EXCEPTION("Unable to send INFO command to specific node", 0);
2795         efree(cmd);
2796         RETURN_FALSE;
2797     }
2798 
2799     if (CLUSTER_IS_ATOMIC(c)) {
2800         cluster_info_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
2801     } else {
2802         CLUSTER_ENQUEUE_RESPONSE(c, slot, cluster_info_resp, ctx);
2803     }
2804 
2805     efree(cmd);
2806 }
2807 /* }}} */
2808 
2809 /* {{{ proto array RedisCluster::client('list')
2810  *     proto bool RedisCluster::client('kill', $ipport)
2811  *     proto bool RedisCluster::client('setname', $name)
2812  *     proto string RedisCluster::client('getname')
2813  */
PHP_METHOD(RedisCluster,client)2814 PHP_METHOD(RedisCluster, client) {
2815     redisCluster *c = GET_CONTEXT();
2816     char *cmd, *opt = NULL, *arg = NULL;
2817     int cmd_len;
2818     size_t opt_len, arg_len = 0;
2819     REDIS_REPLY_TYPE rtype;
2820     zval *z_node;
2821     short slot;
2822     cluster_cb cb;
2823 
2824     /* Parse args */
2825     if (zend_parse_parameters(ZEND_NUM_ARGS(), "zs|s", &z_node, &opt,
2826                               &opt_len, &arg, &arg_len) == FAILURE)
2827     {
2828         RETURN_FALSE;
2829     }
2830 
2831     /* Make sure we can properly resolve the slot */
2832     slot = cluster_cmd_get_slot(c, z_node);
2833     if (slot < 0) RETURN_FALSE;
2834 
2835     /* Our return type and reply callback is different for all subcommands */
2836     if (opt_len == 4 && !strncasecmp(opt, "list", 4)) {
2837         rtype = CLUSTER_IS_ATOMIC(c) ? TYPE_BULK : TYPE_LINE;
2838         cb = cluster_client_list_resp;
2839     } else if ((opt_len == 4 && !strncasecmp(opt, "kill", 4)) ||
2840                (opt_len == 7 && !strncasecmp(opt, "setname", 7)))
2841     {
2842         rtype = TYPE_LINE;
2843         cb = cluster_bool_resp;
2844     } else if (opt_len == 7 && !strncasecmp(opt, "getname", 7)) {
2845         rtype = CLUSTER_IS_ATOMIC(c) ? TYPE_BULK : TYPE_LINE;
2846         cb = cluster_bulk_resp;
2847     } else {
2848         php_error_docref(NULL, E_WARNING,
2849             "Invalid CLIENT subcommand (LIST, KILL, GETNAME, and SETNAME are valid");
2850         RETURN_FALSE;
2851     }
2852 
2853     /* Construct the command */
2854     if (ZEND_NUM_ARGS() == 3) {
2855         cmd_len = redis_spprintf(NULL, NULL, &cmd, "CLIENT", "ss",
2856             opt, opt_len, arg, arg_len);
2857     } else if (ZEND_NUM_ARGS() == 2) {
2858         cmd_len = redis_spprintf(NULL, NULL, &cmd, "CLIENT", "s",
2859             opt, opt_len);
2860     } else {
2861         zend_wrong_param_count();
2862         RETURN_FALSE;
2863     }
2864 
2865     /* Attempt to write our command */
2866     if (cluster_send_slot(c, slot, cmd, cmd_len, rtype) < 0) {
2867         CLUSTER_THROW_EXCEPTION("Unable to send CLIENT command to specific node", 0);
2868         efree(cmd);
2869         RETURN_FALSE;
2870     }
2871 
2872     /* Now enqueue or process response */
2873     if (CLUSTER_IS_ATOMIC(c)) {
2874         cb(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
2875     } else {
2876         void *ctx = NULL;
2877         CLUSTER_ENQUEUE_RESPONSE(c, slot, cb, ctx);
2878     }
2879 
2880     efree(cmd);
2881 }
2882 
2883 /* {{{ proto mixed RedisCluster::cluster(variant) */
PHP_METHOD(RedisCluster,cluster)2884 PHP_METHOD(RedisCluster, cluster) {
2885     cluster_raw_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "CLUSTER",
2886         sizeof("CLUSTER")-1);
2887 }
2888 /* }}} */
2889 
2890 /* }}} */
2891 
2892 /* {{{ proto mixed RedisCluster::config(string key, ...)
2893  *     proto mixed RedisCluster::config(array host_port, ...) */
PHP_METHOD(RedisCluster,config)2894 PHP_METHOD(RedisCluster, config) {
2895     cluster_raw_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "CONFIG",
2896         sizeof("CONFIG")-1);
2897 }
2898 /* }}} */
2899 
2900 /* {{{ proto mixed RedisCluster::pubsub(string key, ...)
2901  *     proto mixed RedisCluster::pubsub(array host_port, ...) */
PHP_METHOD(RedisCluster,pubsub)2902 PHP_METHOD(RedisCluster, pubsub) {
2903     cluster_raw_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PUBSUB",
2904         sizeof("PUBSUB")-1);
2905 }
2906 /* }}} */
2907 
2908 /* {{{ proto mixed RedisCluster::script(string key, ...)
2909  *     proto mixed RedisCluster::script(array host_port, ...) */
PHP_METHOD(RedisCluster,script)2910 PHP_METHOD(RedisCluster, script) {
2911     redisCluster *c = GET_CONTEXT();
2912     smart_string cmd = {0};
2913     zval *z_args;
2914     short slot;
2915     int argc = ZEND_NUM_ARGS();
2916 
2917     /* Commands using this pass-thru don't need to be enabled in MULTI mode */
2918     if (!CLUSTER_IS_ATOMIC(c)) {
2919         php_error_docref(0, E_WARNING,
2920             "Command can't be issued in MULTI mode");
2921         RETURN_FALSE;
2922     }
2923 
2924     /* We at least need the key or [host,port] argument */
2925     if (argc < 2) {
2926         php_error_docref(0, E_WARNING,
2927             "Command requires at least an argument to direct to a node");
2928         RETURN_FALSE;
2929     }
2930 
2931     /* Allocate an array to process arguments */
2932     z_args = ecalloc(argc, sizeof(zval));
2933 
2934     /* Grab args */
2935     if (zend_get_parameters_array(ht, argc, z_args) == FAILURE ||
2936         (slot = cluster_cmd_get_slot(c, &z_args[0])) < 0 ||
2937         redis_build_script_cmd(&cmd, argc - 1, &z_args[1]) == NULL
2938     ) {
2939         efree(z_args);
2940         RETURN_FALSE;
2941     }
2942 
2943     /* Send it off */
2944     if (cluster_send_slot(c, slot, cmd.c, cmd.len, TYPE_EOF) < 0) {
2945         CLUSTER_THROW_EXCEPTION("Couldn't send command to node", 0);
2946         efree(cmd.c);
2947         efree(z_args);
2948         RETURN_FALSE;
2949     }
2950 
2951     /* Read the response variant */
2952     cluster_variant_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
2953 
2954     efree(cmd.c);
2955     efree(z_args);
2956 }
2957 /* }}} */
2958 
2959 /* {{{ proto mixed RedisCluster::slowlog(string key, ...)
2960  *     proto mixed RedisCluster::slowlog(array host_port, ...) */
PHP_METHOD(RedisCluster,slowlog)2961 PHP_METHOD(RedisCluster, slowlog) {
2962     cluster_raw_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SLOWLOG",
2963         sizeof("SLOWLOG")-1);
2964 }
2965 /* }}} */
2966 
2967 /* {{{ proto int RedisCluster::geoadd(string key, float long float lat string mem, ...) */
PHP_METHOD(RedisCluster,geoadd)2968 PHP_METHOD(RedisCluster, geoadd) {
2969     CLUSTER_PROCESS_KW_CMD("GEOADD", redis_key_varval_cmd, cluster_long_resp, 0);
2970 }
2971 
2972 /* {{{ proto array RedisCluster::geohash(string key, string mem1, [string mem2...]) */
PHP_METHOD(RedisCluster,geohash)2973 PHP_METHOD(RedisCluster, geohash) {
2974     CLUSTER_PROCESS_KW_CMD("GEOHASH", redis_key_varval_cmd, cluster_mbulk_raw_resp, 1);
2975 }
2976 
2977 /* {{{ proto array RedisCluster::geopos(string key, string mem1, [string mem2...]) */
PHP_METHOD(RedisCluster,geopos)2978 PHP_METHOD(RedisCluster, geopos) {
2979     CLUSTER_PROCESS_KW_CMD("GEOPOS", redis_key_varval_cmd, cluster_variant_resp, 1);
2980 }
2981 
2982 /* {{{ proto array RedisCluster::geodist(string key, string mem1, string mem2 [string unit]) */
PHP_METHOD(RedisCluster,geodist)2983 PHP_METHOD(RedisCluster, geodist) {
2984     CLUSTER_PROCESS_CMD(geodist, cluster_dbl_resp, 1);
2985 }
2986 
2987 /* {{{ proto array RedisCluster::georadius() }}} */
PHP_METHOD(RedisCluster,georadius)2988 PHP_METHOD(RedisCluster, georadius) {
2989     CLUSTER_PROCESS_KW_CMD("GEORADIUS", redis_georadius_cmd, cluster_variant_resp, 1);
2990 }
2991 
2992 /* {{{ proto array RedisCluster::georadius() }}} */
PHP_METHOD(RedisCluster,georadius_ro)2993 PHP_METHOD(RedisCluster, georadius_ro) {
2994     CLUSTER_PROCESS_KW_CMD("GEORADIUS_RO", redis_georadius_cmd, cluster_variant_resp, 1);
2995 }
2996 
2997 /* {{{ proto array RedisCluster::georadiusbymember() }}} */
PHP_METHOD(RedisCluster,georadiusbymember)2998 PHP_METHOD(RedisCluster, georadiusbymember) {
2999     CLUSTER_PROCESS_KW_CMD("GEORADIUSBYMEMBER", redis_georadiusbymember_cmd, cluster_variant_resp, 1);
3000 }
3001 
3002 /* {{{ proto array RedisCluster::georadiusbymember() }}} */
PHP_METHOD(RedisCluster,georadiusbymember_ro)3003 PHP_METHOD(RedisCluster, georadiusbymember_ro) {
3004     CLUSTER_PROCESS_KW_CMD("GEORADIUSBYMEMBER_RO", redis_georadiusbymember_cmd, cluster_variant_resp, 1);
3005 }
3006 
3007 /* {{{ proto array RedisCluster::role(string key)
3008  *     proto array RedisCluster::role(array host_port) */
PHP_METHOD(RedisCluster,role)3009 PHP_METHOD(RedisCluster, role) {
3010     cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ROLE",
3011         TYPE_MULTIBULK, cluster_variant_resp);
3012 }
3013 
3014 /* {{{ proto array RedisCluster::time(string key)
3015  *     proto array RedisCluster::time(array host_port) */
PHP_METHOD(RedisCluster,time)3016 PHP_METHOD(RedisCluster, time) {
3017     cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "TIME",
3018         TYPE_MULTIBULK, cluster_variant_resp);
3019 }
3020 /* }}} */
3021 
3022 /* {{{ proto string RedisCluster::randomkey(string key)
3023  *     proto string RedisCluster::randomkey(array host_port) */
PHP_METHOD(RedisCluster,randomkey)3024 PHP_METHOD(RedisCluster, randomkey) {
3025     cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "RANDOMKEY",
3026         TYPE_BULK, cluster_bulk_resp);
3027 }
3028 /* }}} */
3029 
3030 /* {{{ proto bool RedisCluster::ping(string key| string msg)
3031  *     proto bool RedisCluster::ping(array host_port| string msg) */
PHP_METHOD(RedisCluster,ping)3032 PHP_METHOD(RedisCluster, ping) {
3033     redisCluster *c = GET_CONTEXT();
3034     REDIS_REPLY_TYPE rtype;
3035     void *ctx = NULL;
3036     zval *z_node;
3037     char *cmd, *arg = NULL;
3038     int cmdlen;
3039     size_t arglen;
3040     short slot;
3041 
3042     if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|s!", &z_node, &arg,
3043                               &arglen) == FAILURE)
3044     {
3045         RETURN_FALSE;
3046     }
3047 
3048     /* Treat this as a readonly command */
3049     c->readonly = CLUSTER_IS_ATOMIC(c);
3050 
3051     /* Grab slot either by key or host/port */
3052     slot = cluster_cmd_get_slot(c, z_node);
3053     if (slot < 0) {
3054         RETURN_FALSE;
3055     }
3056 
3057     /* Construct our command */
3058     if (arg != NULL) {
3059         cmdlen = redis_spprintf(NULL, NULL, &cmd, "PING", "s", arg, arglen);
3060     } else {
3061         cmdlen = redis_spprintf(NULL, NULL, &cmd, "PING", "");
3062     }
3063 
3064     /* Send it off */
3065     rtype = CLUSTER_IS_ATOMIC(c) && arg != NULL ? TYPE_BULK : TYPE_LINE;
3066     if (cluster_send_slot(c, slot, cmd, cmdlen, rtype) < 0) {
3067         CLUSTER_THROW_EXCEPTION("Unable to send command at the specified node", 0);
3068         efree(cmd);
3069         RETURN_FALSE;
3070     }
3071 
3072     /* We're done with our command */
3073     efree(cmd);
3074 
3075     /* Process response */
3076     if (CLUSTER_IS_ATOMIC(c)) {
3077         if (arg != NULL) {
3078             cluster_bulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
3079         } else {
3080             /* If we're atomic and didn't send an argument then we have already
3081              * processed the reply (which must have been successful. */
3082             RETURN_TRUE;
3083         }
3084     } else {
3085         if (arg != NULL) {
3086             CLUSTER_ENQUEUE_RESPONSE(c, slot, cluster_bulk_resp, ctx);
3087         } else {
3088             CLUSTER_ENQUEUE_RESPONSE(c, slot, cluster_variant_resp, ctx);
3089         }
3090 
3091         RETURN_ZVAL(getThis(), 1, 0);
3092     }
3093 }
3094 /* }}} */
3095 
3096 /* {{{ proto long RedisCluster::xack(string key, string group, array ids) }}} */
PHP_METHOD(RedisCluster,xack)3097 PHP_METHOD(RedisCluster, xack) {
3098     CLUSTER_PROCESS_CMD(xack, cluster_long_resp, 0);
3099 }
3100 
3101 /* {{{ proto string RedisCluster::xadd(string key, string id, array field_values) }}} */
PHP_METHOD(RedisCluster,xadd)3102 PHP_METHOD(RedisCluster, xadd) {
3103     CLUSTER_PROCESS_CMD(xadd, cluster_bulk_raw_resp, 0);
3104 }
3105 
3106 /* {{{ proto array RedisCluster::xclaim(string key, string group, string consumer,
3107  *                                      long min_idle_time, array ids, array options) */
PHP_METHOD(RedisCluster,xclaim)3108 PHP_METHOD(RedisCluster, xclaim) {
3109     CLUSTER_PROCESS_CMD(xclaim, cluster_xclaim_resp, 0);
3110 }
3111 
PHP_METHOD(RedisCluster,xdel)3112 PHP_METHOD(RedisCluster, xdel) {
3113     CLUSTER_PROCESS_KW_CMD("XDEL", redis_key_str_arr_cmd, cluster_long_resp, 0);
3114 }
3115 
3116 /* {{{ proto variant RedisCluster::xgroup(string op, [string key, string arg1, string arg2]) }}} */
PHP_METHOD(RedisCluster,xgroup)3117 PHP_METHOD(RedisCluster, xgroup) {
3118     CLUSTER_PROCESS_CMD(xgroup, cluster_variant_resp, 0);
3119 }
3120 
3121 /* {{{ proto variant RedisCluster::xinfo(string op, [string arg1, string arg2]); */
PHP_METHOD(RedisCluster,xinfo)3122 PHP_METHOD(RedisCluster, xinfo) {
3123     CLUSTER_PROCESS_CMD(xinfo, cluster_xinfo_resp, 0);
3124 }
3125 
3126 /* {{{ proto string RedisCluster::xlen(string key) }}} */
PHP_METHOD(RedisCluster,xlen)3127 PHP_METHOD(RedisCluster, xlen) {
3128     CLUSTER_PROCESS_KW_CMD("XLEN", redis_key_cmd, cluster_long_resp, 1);
3129 }
3130 
PHP_METHOD(RedisCluster,xpending)3131 PHP_METHOD(RedisCluster, xpending) {
3132     CLUSTER_PROCESS_CMD(xpending, cluster_variant_resp_strings, 1);
3133 }
3134 
PHP_METHOD(RedisCluster,xrange)3135 PHP_METHOD(RedisCluster, xrange) {
3136     CLUSTER_PROCESS_KW_CMD("XRANGE", redis_xrange_cmd, cluster_xrange_resp, 1);
3137 }
3138 
PHP_METHOD(RedisCluster,xrevrange)3139 PHP_METHOD(RedisCluster, xrevrange) {
3140     CLUSTER_PROCESS_KW_CMD("XREVRANGE", redis_xrange_cmd, cluster_xrange_resp, 1);
3141 }
3142 
PHP_METHOD(RedisCluster,xread)3143 PHP_METHOD(RedisCluster, xread) {
3144     CLUSTER_PROCESS_CMD(xread, cluster_xread_resp, 1);
3145 }
3146 
PHP_METHOD(RedisCluster,xreadgroup)3147 PHP_METHOD(RedisCluster, xreadgroup) {
3148     CLUSTER_PROCESS_CMD(xreadgroup, cluster_xread_resp, 0);
3149 }
3150 
PHP_METHOD(RedisCluster,xtrim)3151 PHP_METHOD(RedisCluster, xtrim) {
3152     CLUSTER_PROCESS_CMD(xtrim, cluster_long_resp, 0);
3153 }
3154 
3155 
3156 
3157 /* {{{ proto string RedisCluster::echo(string key, string msg)
3158  *     proto string RedisCluster::echo(array host_port, string msg) */
PHP_METHOD(RedisCluster,echo)3159 PHP_METHOD(RedisCluster, echo) {
3160     redisCluster *c = GET_CONTEXT();
3161     REDIS_REPLY_TYPE rtype;
3162     zval *z_arg;
3163     char *cmd, *msg;
3164     int cmd_len;
3165     size_t msg_len;
3166     short slot;
3167 
3168     if (zend_parse_parameters(ZEND_NUM_ARGS(), "zs", &z_arg, &msg,
3169                              &msg_len) == FAILURE)
3170     {
3171         RETURN_FALSE;
3172     }
3173 
3174     /* Treat this as a readonly command */
3175     c->readonly = CLUSTER_IS_ATOMIC(c);
3176 
3177     /* Grab slot either by key or host/port */
3178     slot = cluster_cmd_get_slot(c, z_arg);
3179     if (slot < 0) {
3180         RETURN_FALSE;
3181     }
3182 
3183     /* Construct our command */
3184     cmd_len = redis_spprintf(NULL, NULL, &cmd, "ECHO", "s", msg, msg_len);
3185 
3186     /* Send it off */
3187     rtype = CLUSTER_IS_ATOMIC(c) ? TYPE_BULK : TYPE_LINE;
3188     if (cluster_send_slot(c,slot,cmd,cmd_len,rtype) < 0) {
3189         CLUSTER_THROW_EXCEPTION("Unable to send command at the specified node", 0);
3190         efree(cmd);
3191         RETURN_FALSE;
3192     }
3193 
3194     /* Process bulk response */
3195     if (CLUSTER_IS_ATOMIC(c)) {
3196         cluster_bulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
3197     } else {
3198         void *ctx = NULL;
3199         CLUSTER_ENQUEUE_RESPONSE(c, slot, cluster_bulk_resp, ctx);
3200     }
3201 
3202     efree(cmd);
3203 }
3204 /* }}} */
3205 
3206 /* {{{ proto mixed RedisCluster::rawcommand(string $key, string $cmd, [ $argv1 .. $argvN])
3207  *     proto mixed RedisCluster::rawcommand(array $host_port, string $cmd, [ $argv1 .. $argvN]) */
PHP_METHOD(RedisCluster,rawcommand)3208 PHP_METHOD(RedisCluster, rawcommand) {
3209     REDIS_REPLY_TYPE rtype;
3210     int argc = ZEND_NUM_ARGS(), cmd_len;
3211     redisCluster *c = GET_CONTEXT();
3212     char *cmd = NULL;
3213     zval *z_args;
3214     short slot;
3215 
3216     /* Sanity check on our arguments */
3217     if (argc < 2) {
3218         php_error_docref(NULL, E_WARNING,
3219             "You must pass at least node information as well as at least a command.");
3220         RETURN_FALSE;
3221     }
3222     z_args = emalloc(argc * sizeof(zval));
3223     if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
3224         php_error_docref(NULL, E_WARNING,
3225             "Internal PHP error parsing method parameters.");
3226         efree(z_args);
3227         RETURN_FALSE;
3228     } else if (redis_build_raw_cmd(&z_args[1], argc-1, &cmd, &cmd_len) ||
3229                (slot = cluster_cmd_get_slot(c, &z_args[0])) < 0)
3230     {
3231         if (cmd) efree(cmd);
3232         efree(z_args);
3233         RETURN_FALSE;
3234     }
3235 
3236     /* Free argument array */
3237     efree(z_args);
3238 
3239     /* Direct the command */
3240     rtype = CLUSTER_IS_ATOMIC(c) ? TYPE_EOF : TYPE_LINE;
3241     if (cluster_send_slot(c,slot,cmd,cmd_len,rtype) < 0) {
3242         CLUSTER_THROW_EXCEPTION("Unable to send command to the specified node", 0);
3243         efree(cmd);
3244         RETURN_FALSE;
3245     }
3246 
3247     /* Process variant response */
3248     if (CLUSTER_IS_ATOMIC(c)) {
3249         cluster_variant_raw_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
3250     } else {
3251         void *ctx = NULL;
3252         CLUSTER_ENQUEUE_RESPONSE(c, slot, cluster_variant_raw_resp, ctx);
3253     }
3254 
3255     efree(cmd);
3256 }
3257 /* }}} */
3258 
3259 /* {{{ proto array RedisCluster::command()
3260  *     proto array RedisCluster::command('INFO', string cmd)
3261  *     proto array RedisCluster::command('GETKEYS', array cmd_args) */
PHP_METHOD(RedisCluster,command)3262 PHP_METHOD(RedisCluster, command) {
3263     CLUSTER_PROCESS_CMD(command, cluster_variant_resp, 0);
3264 }
3265 
3266 /* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */
3267 
3268