.
69 |
--------------------------------------------------------------------------------
/CREDITS:
--------------------------------------------------------------------------------
1 | Redis client extension for PHP
2 | Alfonso Jimenez, Nasreddine Bouafif, Nicolas Favre-Felix, Michael Grunder
3 |
--------------------------------------------------------------------------------
/arrays.markdown:
--------------------------------------------------------------------------------
1 | Redis Arrays
2 | ============
3 |
4 | A Redis array is an isolated namespace in which keys are related in some manner. Keys are distributed across a number of Redis instances, using consistent hashing. A hash function is used to spread the keys across the array in order to keep a uniform distribution. **This feature was added as the result of a generous sponsorship by [A+E Networks](http://www.aetn.com/).**
5 |
6 | An array is composed of the following:
7 |
8 | * A list of Redis hosts.
9 | * A key extraction function, used to hash part of the key in order to distribute related keys on the same node (optional). This is set by the "function" option.
10 | * A list of nodes previously in the ring, only present after a node has been added or removed. When a read command is sent to the array (e.g. GET, LRANGE...), the key is first queryied in the main ring, and then in the secondary ring if it was not found in the main one. Optionally, the keys can be migrated automatically when this happens. Write commands will always go to the main ring. This is set by the "previous" option.
11 | * An optional index in the form of a Redis set per node, used to migrate keys when nodes are added or removed; set by the "index" option.
12 | * An option to rehash the array automatically as nodes are added or removed, set by the "autorehash" option.
13 |
14 | ## Creating an array
15 |
16 | There are several ways of creating Redis arrays; they can be pre-defined in redis.ini using `new RedisArray(string $name);`, or created dynamically using `new RedisArray(array $hosts, array $options);`
17 |
18 | #### Declaring a new array with a list of nodes
19 |
20 | $ra = new RedisArray(array("host1", "host2:63792", "host2:6380"));
21 |
22 |
23 |
24 | #### Declaring a new array with a list of nodes and a function to extract a part of the key
25 |
26 | function extract_key_part($k) {
27 | return substr($k, 0, 3); // hash only on first 3 characters.
28 | }
29 | $ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("function" => "extract_key_part"));
30 |
31 |
32 | #### Defining a "previous" array when nodes are added or removed.
33 | When a new node is added to an array, phpredis needs to know about it. The old list of nodes becomes the “previous” array, and the new list of nodes is used as a main ring. Right after a node has been added, some read commands will point to the wrong nodes and will need to look up the keys in the previous ring.
34 |
35 |
36 | // adding host3 to a ring containing host1 and host2. Read commands will look in the previous ring if the data is not found in the main ring.
37 | $ra = new RedisArray(array("host1", "host2", "host3"), array("previous" => array("host1", "host2")));
38 |
39 |
40 | #### Specifying the "retry_interval" parameter
41 | The retry_interval is used to specify a delay in milliseconds between reconnection attempts in case the client loses connection with a server
42 |
43 | $ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("retry_timeout" => 100)));
44 |
45 |
46 | #### Specifying the "lazy_connect" parameter
47 | This option is useful when a cluster has many shards but not of them are necessarily used at one time.
48 |
49 | $ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("lazy_connect" => true)));
50 |
51 |
52 | #### Defining arrays in Redis.ini
53 |
54 | Because php.ini parameters must be pre-defined, Redis Arrays must all share the same .ini settings.
55 |
56 |
57 | // list available Redis Arrays
58 | ini_set('redis.array.names', 'users,friends');
59 |
60 | // set host names for each array.
61 | ini_set('redis.arrays.hosts', 'users[]=localhost:6379&users[]=localhost:6380&users[]=localhost:6381&users[]=localhost:6382&friends[]=localhost');
62 |
63 | // set functions
64 | ini_set('redis.arrays.functions', 'users=user_hash');
65 |
66 | // use index only for users
67 | ini_set('redis.arrays.index', 'users=1,friends=0');
68 |
69 |
70 | ## Usage
71 |
72 | Redis arrays can be used just as Redis objects:
73 |
74 | $ra = new RedisArray("users");
75 | $ra->set("user1:name", "Joe");
76 | $ra->set("user2:name", "Mike");
77 |
78 |
79 |
80 | ## Key hashing
81 | By default and in order to be compatible with other libraries, phpredis will try to find a substring enclosed in curly braces within the key name, and use it to distribute the data.
82 |
83 | For instance, the keys “{user:1}:name” and “{user:1}:email” will be stored on the same server as only “user:1” will be hashed. You can provide a custom function name in your redis array with the "function" option; this function will be called every time a key needs to be hashed. It should take a string and return a string.
84 |
85 |
86 | ## Custom key distribution function
87 | In order to control the distribution of keys by hand, you can provide a custom function or closure that returns the server number, which is the index in the array of servers that you created the RedisArray object with.
88 |
89 | For instance, instanciate a RedisArray object with `new RedisArray(array("us-host", "uk-host", "de-host"), array("distributor" => "dist"));` and write a function called "dist" that will return `2` for all the keys that should end up on the "de-host" server.
90 |
91 | ### Example
92 |
93 | $ra = new RedisArray(array("host1", "host2", "host3", "host4", "host5", "host6", "host7", "host8"), array("distributor" => array(2, 2)));
94 |
95 |
96 | This declares that we started with 2 shards and moved to 4 then 8 shards. The number of initial shards is 2 and the resharding level (or number of iterations) is 2.
97 |
98 | ## Migrating keys
99 |
100 | When a node is added or removed from a ring, RedisArray instances must be instanciated with a “previous” list of nodes. A single call to `$ra->_rehash()` causes all the keys to be redistributed according to the new list of nodes. Passing a callback function to `_rehash()` makes it possible to track the progress of that operation: the function is called with a node name and a number of keys that will be examined, e.g. `_rehash(function ($host, $count){ ... });`.
101 |
102 | It is possible to automate this process, by setting `'autorehash' => TRUE` in the constructor options. This will cause keys to be migrated when they need to be read from the previous array.
103 |
104 | In order to migrate keys, they must all be examined and rehashed. If the "index" option was set, a single key per node lists all keys present there. Otherwise, the `KEYS` command is used to list them.
105 | If a “previous” list of servers is provided, it will be used as a backup ring when keys can not be found in the current ring. Writes will always go to the new ring, whilst reads will go to the new ring first, and to the second ring as a backup.
106 |
107 | Adding and/or removing several instances is supported.
108 |
109 | ### Example
110 |
111 | $ra = new RedisArray("users"); // load up a new config from redis.ini, using the “.previous” listing.
112 | $ra->_rehash();
113 |
114 |
115 | Running this code will:
116 |
117 | * Create a new ring with the updated list of nodes.
118 | * Server by server, look up all the keys in the previous list of nodes.
119 | * Rehash each key and possibly move it to another server.
120 | * Update the array object with the new list of nodes.
121 |
122 | ## Multi/exec
123 | Multi/exec is still available, but must be run on a single node:
124 |
125 | $host = $ra->_target("{users}:user1:name"); // find host first
126 | $ra->multi($host) // then run transaction on that host.
127 | ->del("{users}:user1:name")
128 | ->srem("{users}:index", "user1")
129 | ->exec();
130 |
131 |
132 | ## Limitations
133 | Key arrays offer no guarantee when using Redis commands that span multiple keys. Except for the use of MGET, MSET, and DEL, a single connection will be used and all the keys read or written there. Running KEYS() on a RedisArray object will execute the command on each node and return an associative array of keys, indexed by host name.
134 |
135 | ## Array info
136 | RedisArray objects provide several methods to help understand the state of the cluster. These methods start with an underscore.
137 |
138 | * `$ra->_hosts()` → returns a list of hosts for the selected array.
139 | * `$ra->_function()` → returns the name of the function used to extract key parts during consistent hashing.
140 | * `$ra->_target($key)` → returns the host to be used for a certain key.
141 | * `$ra->_instance($host)` → returns a redis instance connected to a specific node; use with `_target` to get a single Redis object.
142 |
143 | ## Running the unit tests
144 |
145 | $ cd tests
146 | $ ./mkring.sh start
147 | $ php array-tests.php
148 |
149 |
150 |
--------------------------------------------------------------------------------
/bybits.php:
--------------------------------------------------------------------------------
1 | $count) {
23 | echo $proto . "\t" . $count . "\n";
24 | }
25 | ?>
26 |
--------------------------------------------------------------------------------
/cluster.markdown:
--------------------------------------------------------------------------------
1 | Redis Cluster
2 | =============
3 |
4 | Redis introduces cluster support as of version 3.0.0, and to communicate with a cluster using phpredis one needs to use the RedisCluster class. For the majority of operations the RedisCluster class can act as a drop-in replacement for the Redis class without needing to modify how it's called. **This feature was added as the result of a generous sponsorship by [Tradsey](https://www.tradesy.com/)**
5 |
6 | ## Creating and connecting to a cluster
7 |
8 | To maintain consistency with the RedisArray class, one can create and connect to a cluster either by passing it one or more 'seed' nodes, or by defining these in redis.ini as a 'named' cluster.
9 |
10 | #### Declaring a cluster with an array of seeds
11 |
12 | // Create a cluster setting two nodes as seeds
13 | $obj_cluster = new RedisCluster(NULL, Array('host:7000', 'host:7001', 'host:7003'));
14 |
15 | // Connect and specify timeout and read_timeout
16 | $obj_cluster = new RedisCluster(
17 | NULL, Array("host:7000", "host:7001", 1.5, 1.5);
18 | );
19 |
20 |
21 | #### Loading a cluster configuration by name
22 | In order to load a named array, one must first define the seed nodes in redis.ini. The following lines would define the cluster 'mycluster', and be loaded automatically by phpredis.
23 |
24 |
25 | # In redis.ini
26 | redis.clusters.seeds = "mycluster[]=localhost:7000&test[]=localhost:7001"
27 | redis.clusters.timeout = "mycluster=5"
28 | redis.clusters.read_timeout = "mycluster=10"
29 |
30 |
31 | Then, this cluster can be loaded by doing the following
32 |
33 |
34 | $obj_cluster = new RedisCluster('mycluster');
35 |
36 |
37 | ## Connection process
38 |
39 | On construction, the RedisCluster class will iterate over the provided seed nodes until it can attain a connection to the cluster and run CLUSTER SLOTS to map every node in the cluster locally. Once the keyspace is mapped, RedisCluster will only connect to nodes when it needs to (e.g. you're getting a key that we believe is on that node.)
40 |
41 | ## Timeouts
42 | Because Redis cluster is intended to provide high availability, timeouts do not work in the same way they do in normal socket communication. It's fully possible to have a timeout or even exception on a given socket (say in the case that a master node has failed), and continue to serve the request if and when a slave can be promoted as the new master.
43 |
44 | The way RedisCluster handles user specified timeout values is that every time a command is sent to the cluster, we record the the time at the start of the request and then again every time we have to re-issue the command to a different node (either because Redis cluster responded with MOVED/ASK or because we failed to communicate with a given node). Once we detect having been in the command loop for longer than our specified timeout, an error is raised.
45 |
46 | ## Keyspace map
47 | As previously described, RedisCluster makes an initial mapping of every master (and any slaves) on construction, which it uses to determine which nodes to direct a given command. However, one of the core functionalities of Redis cluster is that this keyspace can change while the cluster is running.
48 |
49 | Because of this, the RedisCluster class will update it's keyspace mapping whenever it receives a MOVED error when requesting data. In the case that we receive ASK redirection, it follows the Redis specification and requests the key from the ASK node, prefixed with an ASKING command.
50 |
51 | ## Automatic slave failover / distribution
52 | By default, RedisCluster will only ever send commands to master nodes, but can be configured differently for readonly commands if requested.
53 |
54 |
55 | // The default option, only send commands to master nodes
56 | $obj_cluster->setOption(RedisCluster::OPT_FAILOVER, RedisCluster::FAILOVER_NONE);
57 |
58 | // In the event we can't reach a master, and it has slaves, failover for read commands
59 | $obj_cluster->setOption(RedisCluster::OPT_FAILOVER, RedisCluster::FAILOVER_ERROR);
60 |
61 | // Always distribute readonly commands between masters and slaves, at random
62 | $obj_cluster->setOption(
63 | RedisCluster::OPT_FAILOVER, RedsiCluster::FAILOVER_DISTRIBUTE
64 | );
65 |
66 |
67 | ## Main command loop
68 | With the exception of commands that are directed to a specific node, each command executed via RedisCluster is processed through a command loop, where we make the request, handle any MOVED or ASK redirection, and repeat if necessary. This continues until one of the following conditions is met:
69 |
70 | 1. We fail to communicate with *any* node that we are aware of, in which case a ```RedisClusterExecption``` is raised.
71 | 2. We have been bounced around longer than the timeout which was set on construction.
72 | 3. Redis cluster returns us a ```CLUSTERDOWN``` error, in which case a ```RedisClusterException``` is raised.
73 | 4. We receive a valid response, in which case the data is returned to the caller.
74 |
75 | ## Transactions
76 | The RedisCluster class fully supports MULTI ... EXEC transactions, including commands such as MGET and MSET which operate on multiple keys. There are considerations that must be taken into account here however.
77 |
78 | When you call ```RedisCluster->multi()```, the cluster is put into a MULTI state, but the MULTI command is not delivered to any nodes until a key is requested on that node. In addition, calls to EXEC will always return an array (even in the event that a transaction to a given node failed), as the commands can be going to any number of nodes depending on what is called.
79 |
80 | Consider the following example:
81 |
82 |
83 | // Cluster is put into MULTI state locally
84 | $obj_cluster->multi();
85 |
86 | // The cluster will issue MULTI on this node first (and only once)
87 | $obj_cluster->get("mykey");
88 | $obj_cluster->set("mykey", "new_value");
89 |
90 | // If 'myotherkey' maps to a different node, MULTI will be issued there
91 | // before requesting the key
92 | $obj_cluster->get("myotherkey");
93 |
94 | // This will always return an array, even in the event of a failed transaction
95 | // on one of the nodes, in which case that element will be FALSE
96 | print_r($obj_cluster->exec());
97 |
98 |
99 | ## Pipelining
100 | The RedisCluster class does not support pipelining as there is no way to detect whether the keys still live where our map indicates that they do and would therefore be inherently unsafe. It would be possible to implement this support as an option if there is demand for such a feature.
101 |
102 | ## Multiple key commands
103 | Redis cluster does allow commands that operate on multiple keys, but only if all of those keys hash to the same slot. Note that it is not enough that the keys are all on the same node, but must actually hash to the exact same hash slot.
104 |
105 | For all of these multiple key commands (with the exception of MGET and MSET), the RedisCluster class will verify each key maps to the same hash slot and raise a "CROSSSLOT" warning, returning false if they don't.
106 |
107 | ### MGET and MSET
108 | RedisCluster has specialized processing for MGET and MSET which allows you to send any number of keys (hashing to whichever slots) without having to consider where they live. The way this works, is that the RedisCluster class will split the command as it iterates through keys, delivering a subset of commands per each key's slot.
109 |
110 |
111 | // This will be delivered in two commands. First for all of the {hash1} keys,
112 | // and then to grab 'otherkey'
113 | $obj_cluster->mget(Array("{hash1}key1","{hash1}key2","{hash1}key3","otherkey"));
114 |
115 |
116 | This operation can also be done in MULTI mode transparently.
117 |
118 | ## Directed node commands
119 | There are a variety of commands which have to be directed at a specific node. In the case of these commands, the caller can either pass a key (which will be hashed and used to direct our command), or an array with host:port.
120 |
121 |
122 | // This will be directed at the slot/node which would store "mykey"
123 | $obj_cluster->echo("mykey","Hello World!");
124 |
125 | // Here we're iterating all of our known masters, and delivering the command there
126 | foreach ($obj_cluster->_masters() as $arr_master) {
127 | $obj_cluster->echo($arr_master, "Hello: " . implode(':', $arr_master));
128 | }
129 |
130 |
131 | In the case of all commands which need to be directed at a node, the calling convention is identical to the Redis call, except that they require an additional (first) argument in order to deliver the command. Following is a list of each of these commands:
132 |
133 | 1. SAVE
134 | 2. BGSAVE
135 | 3. FLUSHDB
136 | 4. FLUSHALL
137 | 5. DBSIZE
138 | 6. BGREWRITEAOF
139 | 7. LASTSAVE
140 | 8. INFO
141 | 9. CLIENT
142 | 10. CLUSTER
143 | 11. CONFIG
144 | 12. PUBSUB
145 | 13. SLOWLOG
146 | 14. RANDOMKEY
147 | 15. PING
148 |
149 |
--------------------------------------------------------------------------------
/cluster_library.h:
--------------------------------------------------------------------------------
1 | #ifndef _PHPREDIS_CLUSTER_LIBRARY_H
2 | #define _PHPREDIS_CLUSTER_LIBRARY_H
3 |
4 | #include "common.h"
5 |
6 | #ifdef ZTS
7 | #include "TSRM.h"
8 | #endif
9 |
10 | /* Redis cluster hash slots and N-1 which we'll use to find it */
11 | #define REDIS_CLUSTER_SLOTS 16384
12 | #define REDIS_CLUSTER_MOD (REDIS_CLUSTER_SLOTS-1)
13 |
14 | /* Complete representation for various commands in RESP */
15 | #define RESP_MULTI_CMD "*1\r\n$5\r\nMULTI\r\n"
16 | #define RESP_EXEC_CMD "*1\r\n$4\r\nEXEC\r\n"
17 | #define RESP_DISCARD_CMD "*1\r\n$7\r\nDISCARD\r\n"
18 | #define RESP_UNWATCH_CMD "*1\r\n$7\r\nUNWATCH\r\n"
19 | #define RESP_CLUSTER_SLOTS_CMD "*2\r\n$7\r\nCLUSTER\r\n$5\r\nSLOTS\r\n"
20 | #define RESP_ASKING_CMD "*1\r\n$6\r\nASKING\r\n"
21 | #define RESP_READONLY_CMD "*1\r\n$8\r\nREADONLY\r\n"
22 | #define RESP_READWRITE_CMD "*1\r\n$9\r\nREADWRITE\r\n"
23 |
24 | #define RESP_READONLY_CMD_LEN (sizeof(RESP_READONLY_CMD)-1)
25 |
26 | /* MOVED/ASK comparison macros */
27 | #define IS_MOVED(p) (p[0]=='M' && p[1]=='O' && p[2]=='V' && p[3]=='E' && \
28 | p[4]=='D' && p[5]==' ')
29 | #define IS_ASK(p) (p[0]=='A' && p[1]=='S' && p[3]=='K' && p[4]==' ')
30 |
31 | /* MOVED/ASK lengths */
32 | #define MOVED_LEN (sizeof("MOVED ")-1)
33 | #define ASK_LEN (sizeof("ASK ")-1)
34 |
35 | /* Initial allocation size for key distribution container */
36 | #define CLUSTER_KEYDIST_ALLOC 8
37 |
38 | /* Macros to access nodes, sockets, and streams for a given slot */
39 | #define SLOT(c,s) (c->master[s])
40 | #define SLOT_SOCK(c,s) (SLOT(c,s)->sock)
41 | #define SLOT_STREAM(c,s) (SLOT_SOCK(c,s)->stream)
42 | #define SLOT_SLAVES(c,s) (c->master[s]->slaves)
43 |
44 | /* Macros to access socket and stream for the node we're communicating with */
45 | #define CMD_SOCK(c) (c->cmd_sock)
46 | #define CMD_STREAM(c) (c->cmd_sock->stream)
47 |
48 | /* Compare redirection slot information with what we have */
49 | #define CLUSTER_REDIR_CMP(c) \
50 | (SLOT_SOCK(c,c->redir_slot)->port != c->redir_port || \
51 | strlen(SLOT_SOCK(c,c->redir_slot)->host) != c->redir_host_len || \
52 | memcmp(SLOT_SOCK(c,c->redir_slot)->host,c->redir_host,c->redir_host_len))
53 |
54 | /* Lazy connect logic */
55 | #define CLUSTER_LAZY_CONNECT(s) \
56 | if(s->lazy_connect) { \
57 | s->lazy_connect = 0; \
58 | redis_sock_server_open(s, 1); \
59 | }
60 |
61 | /* Clear out our "last error" */
62 | #define CLUSTER_CLEAR_ERROR(c) \
63 | if(c->err) { \
64 | efree(c->err); \
65 | c->err = NULL; \
66 | c->err_len = 0; \
67 | } \
68 | c->clusterdown = 0;
69 |
70 | /* Protected sending of data down the wire to a RedisSock->stream */
71 | #define CLUSTER_SEND_PAYLOAD(sock, buf, len) \
72 | (sock && sock->stream && !redis_check_eof(sock, 1) && \
73 | php_stream_write(sock->stream, buf, len)==len)
74 |
75 | /* Macro to read our reply type character */
76 | #define CLUSTER_VALIDATE_REPLY_TYPE(sock, type) \
77 | (redis_check_eof(sock, 1) == 0 && \
78 | (php_stream_getc(sock->stream) == type))
79 |
80 | /* Reset our last single line reply buffer and length */
81 | #define CLUSTER_CLEAR_REPLY(c) \
82 | *c->line_reply = '\0'; c->reply_len = 0;
83 |
84 | /* Helper to determine if we're in MULTI mode */
85 | #define CLUSTER_IS_ATOMIC(c) (c->flags->mode != MULTI)
86 |
87 | /* Helper that either returns false or adds false in multi mode */
88 | #define CLUSTER_RETURN_FALSE(c) \
89 | if(CLUSTER_IS_ATOMIC(c)) { \
90 | RETURN_FALSE; \
91 | } else { \
92 | add_next_index_bool(&c->multi_resp, 0); \
93 | return; \
94 | }
95 |
96 | /* Helper to either return a bool value or add it to MULTI response */
97 | #define CLUSTER_RETURN_BOOL(c, b) \
98 | if(CLUSTER_IS_ATOMIC(c)) { \
99 | if(b==1) {\
100 | RETURN_TRUE; \
101 | } else {\
102 | RETURN_FALSE; \
103 | } \
104 | } else { \
105 | add_next_index_bool(&c->multi_resp, b); \
106 | }
107 |
108 | /* Helper to respond with a double or add it to our MULTI response */
109 | #define CLUSTER_RETURN_DOUBLE(c, d) \
110 | if(CLUSTER_IS_ATOMIC(c)) { \
111 | RETURN_DOUBLE(d); \
112 | } else { \
113 | add_next_index_double(&c->multi_resp, d); \
114 | }
115 |
116 | /* Helper to return a string value */
117 | #define CLUSTER_RETURN_STRING(c, str, len) \
118 | if(CLUSTER_IS_ATOMIC(c)) { \
119 | RETURN_STRINGL(str, len); \
120 | } else { \
121 | add_next_index_stringl(&c->multi_resp, str, len); \
122 | } \
123 |
124 | /* Return a LONG value */
125 | #define CLUSTER_RETURN_LONG(c, val) \
126 | if(CLUSTER_IS_ATOMIC(c)) { \
127 | RETURN_LONG(val); \
128 | } else { \
129 | add_next_index_long(&c->multi_resp, val); \
130 | }
131 |
132 | /* Macro to clear out a clusterMultiCmd structure */
133 | #define CLUSTER_MULTI_CLEAR(mc) \
134 | mc->cmd.s->len = 0; \
135 | mc->args.s->len = 0; \
136 | mc->argc = 0; \
137 |
138 | /* Initialzie a clusterMultiCmd with a keyword and length */
139 | #define CLUSTER_MULTI_INIT(mc, keyword, keyword_len) \
140 | mc.kw = keyword; \
141 | mc.kw_len = keyword_len; \
142 |
143 | /* Cluster redirection enum */
144 | typedef enum CLUSTER_REDIR_TYPE {
145 | REDIR_NONE,
146 | REDIR_MOVED,
147 | REDIR_ASK
148 | } CLUSTER_REDIR_TYPE;
149 |
150 | /* MULTI BULK response callback typedef */
151 | typedef int (*mbulk_cb)(RedisSock*,zval*,long long, void*);
152 |
153 | /* Specific destructor to free a cluster object */
154 | // void redis_destructor_redis_cluster(zend_rsrc_list_entry *rsrc);
155 |
156 | /* A Redis Cluster master node */
157 | typedef struct redisClusterNode {
158 | /* Our Redis socket in question */
159 | RedisSock *sock;
160 |
161 | /* A slot where one of these lives */
162 | short slot;
163 |
164 | /* Is this a slave node */
165 | unsigned short slave;
166 |
167 | /* A HashTable containing any slaves */
168 | HashTable *slaves;
169 | } redisClusterNode;
170 |
171 | /* Forward declarations */
172 | typedef struct clusterFoldItem clusterFoldItem;
173 |
174 | /* RedisCluster implementation structure */
175 | typedef struct redisCluster {
176 | /* Timeout and read timeout (for normal operations) */
177 | double timeout;
178 | double read_timeout;
179 |
180 | /* How long in milliseconds should we wait when being bounced around */
181 | long waitms;
182 |
183 | /* Are we flagged as being in readonly mode, meaning we could fall back to
184 | * a given master's slave */
185 | short readonly;
186 |
187 | /* RedisCluster failover options (never, on error, to load balance) */
188 | short failover;
189 |
190 | /* Hash table of seed host/ports */
191 | HashTable *seeds;
192 |
193 | /* RedisCluster masters, by direct slot */
194 | redisClusterNode *master[REDIS_CLUSTER_SLOTS];
195 |
196 | /* All RedisCluster objects we've created/are connected to */
197 | HashTable *nodes;
198 |
199 | /* Transaction handling linked list, and where we are as we EXEC */
200 | clusterFoldItem *multi_head;
201 | clusterFoldItem *multi_curr;
202 |
203 | /* When we issue EXEC to nodes, we need to keep track of how many replies
204 | * we have, as this can fail for various reasons (EXECABORT, watch, etc.) */
205 | char multi_len[REDIS_CLUSTER_SLOTS];
206 |
207 | /* Variable to store MULTI response */
208 | zval multi_resp;
209 |
210 | /* Flag for when we get a CLUSTERDOWN error */
211 | short clusterdown;
212 |
213 | /* The last ERROR we encountered */
214 | char *err;
215 | int err_len;
216 |
217 | /* The slot our command is operating on, as well as it's socket */
218 | unsigned short cmd_slot;
219 | RedisSock *cmd_sock;
220 |
221 | /* The slot where we're subscribed */
222 | short subscribed_slot;
223 |
224 | /* One RedisSock struct for serialization and prefix information */
225 | RedisSock *flags;
226 |
227 | /* The first line of our last reply, not including our reply type byte
228 | * or the trailing \r\n */
229 | char line_reply[1024];
230 |
231 | /* The last reply type and length or integer response we got */
232 | REDIS_REPLY_TYPE reply_type;
233 | long long reply_len;
234 |
235 | /* Last MOVED or ASK redirection response information */
236 | CLUSTER_REDIR_TYPE redir_type;
237 | char redir_host[255];
238 | int redir_host_len;
239 | unsigned short redir_slot;
240 | unsigned short redir_port;
241 |
242 | /* Object reference for Zend */
243 | zend_object std;
244 | } redisCluster;
245 |
246 | /* RedisCluster response processing callback */
247 | typedef void (*cluster_cb)(INTERNAL_FUNCTION_PARAMETERS, redisCluster*, void*);
248 |
249 | /* Context for processing transactions */
250 | struct clusterFoldItem {
251 | /* Response processing callback */
252 | cluster_cb callback;
253 |
254 | /* The actual socket where we send this request */
255 | unsigned short slot;
256 |
257 | /* Any context we need to send to our callback */
258 | void *ctx;
259 |
260 | /* Next item in our list */
261 | struct clusterFoldItem *next;
262 | };
263 |
264 | /* Key and value container, with info if they need freeing */
265 | typedef struct clusterKeyVal {
266 | zend_string *key;
267 | zend_string *val;
268 | } clusterKeyVal;
269 |
270 | /* Container to hold keys (and possibly values) for when we need to distribute
271 | * commands across more than 1 node (e.g. WATCH, MGET, MSET, etc) */
272 | typedef struct clusterDistList {
273 | clusterKeyVal *entry;
274 | size_t len, size;
275 | } clusterDistList;
276 |
277 | /* Context for things like MGET/MSET/MSETNX. When executing in MULTI mode,
278 | * we'll want to re-integrate into one running array, except for the last
279 | * command execution, in which we'll want to return the value (or add it) */
280 | typedef struct clusterMultiCtx {
281 | /* Our running array */
282 | zval z_multi;
283 |
284 | /* How many keys did we request for this bit */
285 | int count;
286 |
287 | /* Is this the last entry */
288 | short last;
289 | } clusterMultiCtx;
290 |
291 | /* Container for things like MGET, MSET, and MSETNX, which split the command
292 | * into a header and payload while aggregating to a specific slot. */
293 | typedef struct clusterMultiCmd {
294 | /* Keyword and keyword length */
295 | char *kw;
296 | int kw_len;
297 |
298 | /* Arguments in our payload */
299 | int argc;
300 |
301 | /* The full command, built into cmd, and args as we aggregate */
302 | smart_str cmd;
303 | smart_str args;
304 | } clusterMultiCmd;
305 |
306 | /* Hiredis like structure for processing any sort of reply Redis Cluster might
307 | * give us, including N level deep nested multi-bulk replies. Unlike hiredis
308 | * we don't encode errors, here as that's handled in the cluster structure. */
309 | typedef struct clusterReply {
310 | REDIS_REPLY_TYPE type; /* Our reply type */
311 | size_t integer; /* Integer reply */
312 | long long len; /* Length of our string */
313 | char *str; /* String reply */
314 | size_t elements; /* Count of array elements */
315 | struct clusterReply **element; /* Array elements */
316 | } clusterReply;
317 |
318 | /* Direct variant response handler */
319 | clusterReply *cluster_read_resp(redisCluster *c);
320 | clusterReply *cluster_read_sock_resp(RedisSock *redis_sock,
321 | REDIS_REPLY_TYPE type, size_t reply_len);
322 | void cluster_free_reply(clusterReply *reply, int free_data);
323 |
324 | /* Cluster distribution helpers for WATCH */
325 | HashTable *cluster_dist_create();
326 | void cluster_dist_free(HashTable *ht);
327 | int cluster_dist_add_key(redisCluster *c, HashTable *ht, zend_string *key, clusterKeyVal **kv);
328 | void cluster_dist_add_val(redisCluster *c, clusterKeyVal *kv, zval *val );
329 |
330 | /* Aggregation for multi commands like MGET, MSET, and MSETNX */
331 | void cluster_multi_init(clusterMultiCmd *mc, char *kw, int kw_len);
332 | void cluster_multi_free(clusterMultiCmd *mc);
333 | void cluster_multi_add(clusterMultiCmd *mc, char *data, int data_len);
334 | void cluster_multi_fini(clusterMultiCmd *mc);
335 |
336 | /* Hash a key to it's slot, using the Redis Cluster hash algorithm */
337 | unsigned short cluster_hash_key_zval(zval *key);
338 | unsigned short cluster_hash_key(const char *key, int len);
339 |
340 | /* Get the current time in miliseconds */
341 | long long mstime(void);
342 |
343 | PHP_REDIS_API short cluster_send_command(redisCluster *c, short slot, const char *cmd,
344 | int cmd_len);
345 |
346 | PHP_REDIS_API void cluster_disconnect(redisCluster *c);
347 |
348 | PHP_REDIS_API int cluster_send_exec(redisCluster *c, short slot);
349 | PHP_REDIS_API int cluster_send_discard(redisCluster *c, short slot);
350 | PHP_REDIS_API int cluster_abort_exec(redisCluster *c);
351 | PHP_REDIS_API int cluster_reset_multi(redisCluster *c);
352 |
353 | PHP_REDIS_API short cluster_find_slot(redisCluster *c, const char *host,
354 | unsigned short port);
355 | PHP_REDIS_API int cluster_send_slot(redisCluster *c, short slot, char *cmd,
356 | int cmd_len, REDIS_REPLY_TYPE rtype);
357 |
358 | PHP_REDIS_API int cluster_init_seeds(redisCluster *c, HashTable *ht_seeds);
359 | PHP_REDIS_API int cluster_map_keyspace(redisCluster *c);
360 | PHP_REDIS_API void cluster_free_node(redisClusterNode *node);
361 |
362 | PHP_REDIS_API char **cluster_sock_read_multibulk_reply(RedisSock *redis_sock,
363 | int *len);
364 |
365 | /*
366 | * Redis Cluster response handlers. Our response handlers generally take the
367 | * following form:
368 | * PHP_REDIS_API void handler(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
369 | * void *ctx)
370 | *
371 | * Reply handlers are responsible for setting the PHP return value (either to
372 | * something valid, or FALSE in the case of some failures).
373 | */
374 |
375 | PHP_REDIS_API void cluster_bool_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
376 | void *ctx);
377 | PHP_REDIS_API void cluster_ping_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
378 | void *ctx);
379 | PHP_REDIS_API void cluster_bulk_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
380 | void *ctx);
381 | PHP_REDIS_API void cluster_bulk_raw_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
382 | void *ctx);
383 | PHP_REDIS_API void cluster_dbl_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
384 | void *ctx);
385 | PHP_REDIS_API void cluster_1_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
386 | void *ctx);
387 | PHP_REDIS_API void cluster_long_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
388 | void *ctx);
389 | PHP_REDIS_API void cluster_type_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
390 | void *ctx);
391 | PHP_REDIS_API void cluster_sub_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
392 | void *ctx);
393 | PHP_REDIS_API void cluster_unsub_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
394 | void *ctx);
395 |
396 | /* Generic/Variant handler for stuff like EVAL */
397 | PHP_REDIS_API void cluster_variant_resp(INTERNAL_FUNCTION_PARAMETERS,
398 | redisCluster *c, void *ctx);
399 |
400 | /* MULTI BULK response functions */
401 | PHP_REDIS_API void cluster_gen_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS,
402 | redisCluster *c, mbulk_cb func, void *ctx);
403 | PHP_REDIS_API void cluster_mbulk_raw_resp(INTERNAL_FUNCTION_PARAMETERS,
404 | redisCluster *c, void *ctx);
405 | PHP_REDIS_API void cluster_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS,
406 | redisCluster *c, void *ctx);
407 | PHP_REDIS_API void cluster_mbulk_zipstr_resp(INTERNAL_FUNCTION_PARAMETERS,
408 | redisCluster *c, void *ctx);
409 | PHP_REDIS_API void cluster_mbulk_zipdbl_resp(INTERNAL_FUNCTION_PARAMETERS,
410 | redisCluster *c, void *ctx);
411 | PHP_REDIS_API void cluster_mbulk_assoc_resp(INTERNAL_FUNCTION_PARAMETERS,
412 | redisCluster *c, void *ctx);
413 | PHP_REDIS_API void cluster_multi_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS,
414 | redisCluster *c, void *ctx);
415 | PHP_REDIS_API zval *cluster_zval_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS,
416 | redisCluster *c, int pull, mbulk_cb cb, zval *rv);
417 |
418 | /* Handlers for things like DEL/MGET/MSET/MSETNX */
419 | PHP_REDIS_API void cluster_del_resp(INTERNAL_FUNCTION_PARAMETERS,
420 | redisCluster *c, void *ctx);
421 | PHP_REDIS_API void cluster_mbulk_mget_resp(INTERNAL_FUNCTION_PARAMETERS,
422 | redisCluster *c, void *ctx);
423 | PHP_REDIS_API void cluster_mset_resp(INTERNAL_FUNCTION_PARAMETERS,
424 | redisCluster *c, void *ctx);
425 | PHP_REDIS_API void cluster_msetnx_resp(INTERNAL_FUNCTION_PARAMETERS,
426 | redisCluster *c, void *ctx);
427 |
428 | /* Response handler for ZSCAN, SSCAN, and HSCAN */
429 | PHP_REDIS_API int cluster_scan_resp(INTERNAL_FUNCTION_PARAMETERS,
430 | redisCluster *c, REDIS_SCAN_TYPE type, long *it);
431 |
432 | /* INFO response handler */
433 | PHP_REDIS_API void cluster_info_resp(INTERNAL_FUNCTION_PARAMETERS,
434 | redisCluster *c, void *ctx);
435 |
436 | /* CLIENT LIST response handler */
437 | PHP_REDIS_API void cluster_client_list_resp(INTERNAL_FUNCTION_PARAMETERS,
438 | redisCluster *c, void *ctx);
439 |
440 | /* MULTI BULK processing callbacks */
441 | int mbulk_resp_loop(RedisSock *redis_sock, zval *z_result,
442 | long long count, void *ctx);
443 | int mbulk_resp_loop_raw(RedisSock *redis_sock, zval *z_result,
444 | long long count, void *ctx);
445 | int mbulk_resp_loop_zipstr(RedisSock *redis_sock, zval *z_result,
446 | long long count, void *ctx);
447 | int mbulk_resp_loop_zipdbl(RedisSock *redis_sock, zval *z_result,
448 | long long count, void *ctx);
449 | int mbulk_resp_loop_assoc(RedisSock *redis_sock, zval *z_result,
450 | long long count, void *ctx);
451 |
452 | #endif
453 |
454 | /* vim: set tabstop=4 softtabstop=4 noexpandtab shiftwidth=4: */
455 |
--------------------------------------------------------------------------------
/common.h:
--------------------------------------------------------------------------------
1 | #include "php.h"
2 | #include "php_ini.h"
3 | #include
4 |
5 | #ifndef REDIS_COMMON_H
6 | #define REDIS_COMMON_H
7 |
8 | /* NULL check so Eclipse doesn't go crazy */
9 | #ifndef NULL
10 | #define NULL ((void *) 0)
11 | #endif
12 |
13 | #define redis_sock_name "Redis Socket Buffer"
14 | #define REDIS_SOCK_STATUS_FAILED 0
15 | #define REDIS_SOCK_STATUS_DISCONNECTED 1
16 | #define REDIS_SOCK_STATUS_UNKNOWN 2
17 | #define REDIS_SOCK_STATUS_CONNECTED 3
18 |
19 | #define redis_multi_access_type_name "Redis Multi type access"
20 |
21 | #define _NL "\r\n"
22 |
23 | /* properties */
24 | #define REDIS_NOT_FOUND 0
25 | #define REDIS_STRING 1
26 | #define REDIS_SET 2
27 | #define REDIS_LIST 3
28 | #define REDIS_ZSET 4
29 | #define REDIS_HASH 5
30 |
31 | #ifdef PHP_WIN32
32 | #define PHP_REDIS_API __declspec(dllexport)
33 | #else
34 | #define PHP_REDIS_API
35 | #endif
36 |
37 | /* reply types */
38 | typedef enum _REDIS_REPLY_TYPE {
39 | TYPE_EOF = -1,
40 | TYPE_LINE = '+',
41 | TYPE_INT = ':',
42 | TYPE_ERR = '-',
43 | TYPE_BULK = '$',
44 | TYPE_MULTIBULK = '*'
45 | } REDIS_REPLY_TYPE;
46 |
47 | /* SCAN variants */
48 | typedef enum _REDIS_SCAN_TYPE {
49 | TYPE_SCAN,
50 | TYPE_SSCAN,
51 | TYPE_HSCAN,
52 | TYPE_ZSCAN
53 | } REDIS_SCAN_TYPE;
54 |
55 | /* PUBSUB subcommands */
56 | typedef enum _PUBSUB_TYPE {
57 | PUBSUB_CHANNELS,
58 | PUBSUB_NUMSUB,
59 | PUBSUB_NUMPAT
60 | } PUBSUB_TYPE;
61 |
62 | /* options */
63 | #define REDIS_OPT_SERIALIZER 1
64 | #define REDIS_OPT_PREFIX 2
65 | #define REDIS_OPT_READ_TIMEOUT 3
66 | #define REDIS_OPT_SCAN 4
67 |
68 | /* cluster options */
69 | #define REDIS_OPT_FAILOVER 5
70 | #define REDIS_FAILOVER_NONE 0
71 | #define REDIS_FAILOVER_ERROR 1
72 | #define REDIS_FAILOVER_DISTRIBUTE 2
73 |
74 | /* serializers */
75 | #define REDIS_SERIALIZER_NONE 0
76 | #define REDIS_SERIALIZER_PHP 1
77 | #define REDIS_SERIALIZER_IGBINARY 2
78 |
79 | /* SCAN options */
80 | #define REDIS_SCAN_NORETRY 0
81 | #define REDIS_SCAN_RETRY 1
82 |
83 | /* GETBIT/SETBIT offset range limits */
84 | #define BITOP_MIN_OFFSET 0
85 | #define BITOP_MAX_OFFSET 4294967295
86 |
87 | /* Specific error messages we want to throw against */
88 | #define REDIS_ERR_LOADING_MSG "LOADING Redis is loading the dataset in memory"
89 | #define REDIS_ERR_LOADING_KW "LOADING"
90 | #define REDIS_ERR_AUTH_MSG "NOAUTH Authentication required."
91 | #define REDIS_ERR_AUTH_KW "NOAUTH"
92 | #define REDIS_ERR_SYNC_MSG "MASTERDOWN Link with MASTER is down and slave-serve-stale-data is set to 'no'"
93 | #define REDIS_ERR_SYNC_KW "MASTERDOWN"
94 |
95 | #define IF_MULTI() if(redis_sock->mode == MULTI)
96 | #define IF_MULTI_OR_ATOMIC() if(redis_sock->mode == MULTI || redis_sock->mode == ATOMIC)\
97 |
98 | #define IF_MULTI_OR_PIPELINE() if(redis_sock->mode == MULTI || redis_sock->mode == PIPELINE)
99 | #define IF_PIPELINE() if(redis_sock->mode == PIPELINE)
100 | #define IF_NOT_MULTI() if(redis_sock->mode != MULTI)
101 | #define IF_NOT_ATOMIC() if(redis_sock->mode != ATOMIC)
102 | #define IF_ATOMIC() if(redis_sock->mode == ATOMIC)
103 | #define ELSE_IF_MULTI() else if(redis_sock->mode == MULTI) { \
104 | if(redis_response_enqueued(redis_sock) == 1) { \
105 | RETURN_ZVAL(getThis(), 1, 0);\
106 | } else { \
107 | RETURN_FALSE; \
108 | } \
109 | }
110 |
111 | #define ELSE_IF_PIPELINE() else IF_PIPELINE() { \
112 | RETURN_ZVAL(getThis(), 1, 0);\
113 | }
114 |
115 | #define MULTI_RESPONSE(callback) IF_MULTI_OR_PIPELINE() { \
116 | fold_item *f1, *current; \
117 | f1 = malloc(sizeof(fold_item)); \
118 | f1->fun = (void *)callback; \
119 | f1->next = NULL; \
120 | current = redis_sock->current;\
121 | if(current) current->next = f1; \
122 | redis_sock->current = f1; \
123 | }
124 |
125 | #define PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len) request_item *tmp; \
126 | struct request_item *current_request;\
127 | tmp = malloc(sizeof(request_item));\
128 | tmp->request_str = calloc(cmd_len, 1);\
129 | memcpy(tmp->request_str, cmd, cmd_len);\
130 | tmp->request_size = cmd_len;\
131 | tmp->next = NULL;\
132 | current_request = redis_sock->pipeline_current; \
133 | if(current_request) {\
134 | current_request->next = tmp;\
135 | } \
136 | redis_sock->pipeline_current = tmp; \
137 | if(NULL == redis_sock->pipeline_head) { \
138 | redis_sock->pipeline_head = redis_sock->pipeline_current;\
139 | }
140 |
141 | #define SOCKET_WRITE_COMMAND(redis_sock, cmd, cmd_len) \
142 | if(redis_sock_write(redis_sock, cmd, cmd_len) < 0) { \
143 | efree(cmd); \
144 | RETURN_FALSE; \
145 | }
146 |
147 | #define REDIS_SAVE_CALLBACK(callback, closure_context) \
148 | IF_MULTI_OR_PIPELINE() { \
149 | fold_item *f1, *current; \
150 | f1 = malloc(sizeof(fold_item)); \
151 | f1->fun = (void *)callback; \
152 | f1->ctx = closure_context; \
153 | f1->next = NULL; \
154 | current = redis_sock->current;\
155 | if(current) current->next = f1; \
156 | redis_sock->current = f1; \
157 | if(NULL == redis_sock->head) { \
158 | redis_sock->head = redis_sock->current;\
159 | }\
160 | }
161 |
162 | #define REDIS_ELSE_IF_MULTI(function, closure_context) \
163 | else if(redis_sock->mode == MULTI) { \
164 | if(redis_response_enqueued(redis_sock) == 1) {\
165 | REDIS_SAVE_CALLBACK(function, closure_context); \
166 | RETURN_ZVAL(getThis(), 1, 0);\
167 | } else {\
168 | RETURN_FALSE;\
169 | }\
170 | }
171 |
172 | #define REDIS_ELSE_IF_PIPELINE(function, closure_context) \
173 | else IF_PIPELINE() { \
174 | REDIS_SAVE_CALLBACK(function, closure_context); \
175 | RETURN_ZVAL(getThis(), 1, 0); \
176 | }
177 |
178 | #define REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len) \
179 | IF_MULTI_OR_ATOMIC() { \
180 | SOCKET_WRITE_COMMAND(redis_sock, cmd, cmd_len); \
181 | }\
182 | IF_PIPELINE() { \
183 | PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len); \
184 | }
185 |
186 | #define REDIS_PROCESS_RESPONSE_CLOSURE(function, closure_context) \
187 | REDIS_ELSE_IF_MULTI(function, closure_context) \
188 | REDIS_ELSE_IF_PIPELINE(function, closure_context);
189 |
190 | #define REDIS_PROCESS_RESPONSE(function) \
191 | REDIS_PROCESS_RESPONSE_CLOSURE(function, NULL)
192 |
193 | /* Clear redirection info */
194 | #define REDIS_MOVED_CLEAR(redis_sock) \
195 | redis_sock->redir_slot = 0; \
196 | redis_sock->redir_port = 0; \
197 | redis_sock->redir_type = MOVED_NONE; \
198 |
199 | /* Process a command assuming our command where our command building
200 | * function is redis__cmd */
201 | #define REDIS_PROCESS_CMD(cmdname, resp_func) \
202 | RedisSock *redis_sock; char *cmd; int cmd_len; void *ctx=NULL; \
203 | if(redis_sock_get(getThis(), &redis_sock, 0)<0 || \
204 | redis_##cmdname##_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU,redis_sock, \
205 | &cmd, &cmd_len, NULL, &ctx)==FAILURE) { \
206 | RETURN_FALSE; \
207 | } \
208 | REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); \
209 | IF_ATOMIC() { \
210 | resp_func(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, ctx); \
211 | } \
212 | REDIS_PROCESS_RESPONSE_CLOSURE(resp_func,ctx);
213 |
214 | /* Process a command but with a specific command building function
215 | * and keyword which is passed to us*/
216 | #define REDIS_PROCESS_KW_CMD(kw, cmdfunc, resp_func) \
217 | RedisSock *redis_sock; char *cmd; int cmd_len; void *ctx=NULL; \
218 | if(redis_sock_get(getThis(), &redis_sock, 0)<0 || \
219 | cmdfunc(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, kw, &cmd, \
220 | &cmd_len, NULL, &ctx)==FAILURE) { \
221 | RETURN_FALSE; \
222 | } \
223 | REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); \
224 | IF_ATOMIC() { \
225 | resp_func(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, ctx); \
226 | } \
227 | REDIS_PROCESS_RESPONSE_CLOSURE(resp_func,ctx);
228 |
229 | /* Extended SET argument detection */
230 | #define IS_EX_ARG(a) \
231 | ((a[0]=='e' || a[0]=='E') && (a[1]=='x' || a[1]=='X') && a[2]=='\0')
232 | #define IS_PX_ARG(a) \
233 | ((a[0]=='p' || a[0]=='P') && (a[1]=='x' || a[1]=='X') && a[2]=='\0')
234 | #define IS_NX_ARG(a) \
235 | ((a[0]=='n' || a[0]=='N') && (a[1]=='x' || a[1]=='X') && a[2]=='\0')
236 | #define IS_XX_ARG(a) \
237 | ((a[0]=='x' || a[0]=='X') && (a[1]=='x' || a[1]=='X') && a[2]=='\0')
238 |
239 | #define IS_EX_PX_ARG(a) (IS_EX_ARG(a) || IS_PX_ARG(a))
240 | #define IS_NX_XX_ARG(a) (IS_NX_ARG(a) || IS_XX_ARG(a))
241 |
242 | typedef enum {ATOMIC, MULTI, PIPELINE} redis_mode;
243 |
244 | typedef struct fold_item {
245 | zval * (*fun)(INTERNAL_FUNCTION_PARAMETERS, void *, ...);
246 | void *ctx;
247 | struct fold_item *next;
248 | } fold_item;
249 |
250 | typedef struct request_item {
251 | char *request_str;
252 | int request_size; /* size_t */
253 | struct request_item *next;
254 | } request_item;
255 |
256 | /* {{{ struct RedisSock */
257 | typedef struct {
258 | php_stream *stream;
259 | char *host;
260 | short port;
261 | char *auth;
262 | double timeout;
263 | double read_timeout;
264 | long retry_interval;
265 | int failed;
266 | int status;
267 | int persistent;
268 | int watching;
269 | char *persistent_id;
270 |
271 | int serializer;
272 | long dbNumber;
273 |
274 | char *prefix;
275 | int prefix_len;
276 |
277 | redis_mode mode;
278 | fold_item *head;
279 | fold_item *current;
280 |
281 | request_item *pipeline_head;
282 | request_item *pipeline_current;
283 |
284 | char *err;
285 | int err_len;
286 | zend_bool lazy_connect;
287 |
288 | int scan;
289 |
290 | int readonly;
291 | } RedisSock;
292 | /* }}} */
293 |
294 | void
295 | free_reply_callbacks(zval *z_this, RedisSock *redis_sock);
296 |
297 | #endif
298 |
--------------------------------------------------------------------------------
/config.m4:
--------------------------------------------------------------------------------
1 | dnl $Id$
2 | dnl config.m4 for extension redis
3 |
4 | PHP_ARG_ENABLE(redis, whether to enable redis support,
5 | dnl Make sure that the comment is aligned:
6 | [ --enable-redis Enable redis support])
7 |
8 | PHP_ARG_ENABLE(redis-session, whether to enable sessions,
9 | [ --disable-redis-session Disable session support], yes, no)
10 |
11 | PHP_ARG_ENABLE(redis-igbinary, whether to enable igbinary serializer support,
12 | [ --enable-redis-igbinary Enable igbinary serializer support], no, no)
13 |
14 |
15 | if test "$PHP_REDIS" != "no"; then
16 |
17 | if test "$PHP_REDIS_SESSION" != "no"; then
18 | AC_DEFINE(PHP_SESSION,1,[redis sessions])
19 | fi
20 |
21 | dnl Check for igbinary
22 | if test "$PHP_REDIS_IGBINARY" != "no"; then
23 | AC_MSG_CHECKING([for igbinary includes])
24 | igbinary_inc_path=""
25 |
26 | if test -f "$abs_srcdir/include/php/ext/igbinary/igbinary.h"; then
27 | igbinary_inc_path="$abs_srcdir/include/php"
28 | elif test -f "$abs_srcdir/ext/igbinary/igbinary.h"; then
29 | igbinary_inc_path="$abs_srcdir"
30 | elif test -f "$phpincludedir/ext/igbinary/igbinary.h"; then
31 | igbinary_inc_path="$phpincludedir"
32 | else
33 | for i in php php4 php5 php6; do
34 | if test -f "$prefix/include/$i/ext/igbinary/igbinary.h"; then
35 | igbinary_inc_path="$prefix/include/$i"
36 | fi
37 | done
38 | fi
39 |
40 | if test "$igbinary_inc_path" = ""; then
41 | AC_MSG_ERROR([Cannot find igbinary.h])
42 | else
43 | AC_MSG_RESULT([$igbinary_inc_path])
44 | fi
45 | fi
46 |
47 | AC_MSG_CHECKING([for redis igbinary support])
48 | if test "$PHP_REDIS_IGBINARY" != "no"; then
49 | AC_MSG_RESULT([enabled])
50 | AC_DEFINE(HAVE_REDIS_IGBINARY,1,[Whether redis igbinary serializer is enabled])
51 | IGBINARY_INCLUDES="-I$igbinary_inc_path"
52 | IGBINARY_EXT_DIR="$igbinary_inc_path/ext"
53 | ifdef([PHP_ADD_EXTENSION_DEP],
54 | [
55 | PHP_ADD_EXTENSION_DEP(redis, igbinary)
56 | ])
57 | PHP_ADD_INCLUDE($IGBINARY_EXT_DIR)
58 | else
59 | IGBINARY_INCLUDES=""
60 | AC_MSG_RESULT([disabled])
61 | fi
62 |
63 | dnl # --with-redis -> check with-path
64 | dnl SEARCH_PATH="/usr/local /usr" # you might want to change this
65 | dnl SEARCH_FOR="/include/redis.h" # you most likely want to change this
66 | dnl if test -r $PHP_REDIS/$SEARCH_FOR; then # path given as parameter
67 | dnl REDIS_DIR=$PHP_REDIS
68 | dnl else # search default path list
69 | dnl AC_MSG_CHECKING([for redis files in default path])
70 | dnl for i in $SEARCH_PATH ; do
71 | dnl if test -r $i/$SEARCH_FOR; then
72 | dnl REDIS_DIR=$i
73 | dnl AC_MSG_RESULT(found in $i)
74 | dnl fi
75 | dnl done
76 | dnl fi
77 | dnl
78 | dnl if test -z "$REDIS_DIR"; then
79 | dnl AC_MSG_RESULT([not found])
80 | dnl AC_MSG_ERROR([Please reinstall the redis distribution])
81 | dnl fi
82 |
83 | dnl # --with-redis -> add include path
84 | dnl PHP_ADD_INCLUDE($REDIS_DIR/include)
85 |
86 | dnl # --with-redis -> check for lib and symbol presence
87 | dnl LIBNAME=redis # you may want to change this
88 | dnl LIBSYMBOL=redis # you most likely want to change this
89 |
90 | dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
91 | dnl [
92 | dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $REDIS_DIR/lib, REDIS_SHARED_LIBADD)
93 | dnl AC_DEFINE(HAVE_REDISLIB,1,[ ])
94 | dnl ],[
95 | dnl AC_MSG_ERROR([wrong redis lib version or lib not found])
96 | dnl ],[
97 | dnl -L$REDIS_DIR/lib -lm -ldl
98 | dnl ])
99 | dnl
100 | dnl PHP_SUBST(REDIS_SHARED_LIBADD)
101 |
102 | PHP_NEW_EXTENSION(redis, redis.c redis_commands.c library.c redis_session.c redis_array.c redis_array_impl.c redis_cluster.c cluster_library.c, $ext_shared)
103 | fi
104 |
--------------------------------------------------------------------------------
/config.w32:
--------------------------------------------------------------------------------
1 | // vim: ft=javascript:
2 |
3 | ARG_ENABLE("redis", "whether to enable redis support", "no");
4 | ARG_ENABLE("redis-session", "whether to enable sessions", "yes");
5 | ARG_ENABLE("redis-igbinary", "whether to enable igbinary serializer support", "yes");
6 |
7 | if (PHP_REDIS != "no") {
8 | var sources = "redis.c library.c redis_commands.c redis_array.c redis_array_impl.c redis_cluster.c cluster_library.c";
9 | AC_DEFINE("HAVE_REDIS", 1);
10 |
11 | if (PHP_REDIS_SESSION != "no") {
12 | ADD_SOURCES(configure_module_dirname, "redis_session.c", "redis");
13 | ADD_EXTENSION_DEP("redis", "session");
14 | ADD_FLAG("CFLAGS_REDIS", ' /D PHP_SESSION=1 ');
15 | AC_DEFINE("HAVE_REDIS_SESSION", 1);
16 | }
17 |
18 | if (PHP_REDIS_IGBINARY != "no") {
19 | if (CHECK_HEADER_ADD_INCLUDE("igbinary.h", "CFLAGS_REDIS", configure_module_dirname + "\\..\\igbinary")) {
20 |
21 | ADD_EXTENSION_DEP("redis", "igbinary");
22 | AC_DEFINE("HAVE_REDIS_IGBINARY", 1);
23 | } else {
24 | WARNING("redis igbinary support not enabled");
25 | }
26 | }
27 |
28 | EXTENSION("redis", sources);
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/crc16.h:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2001-2010 Georges Menie (www.menie.org)
3 | * Copyright 2010 Salvatore Sanfilippo (adapted to Redis coding style)
4 | * All rights reserved.
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | *
8 | * * Redistributions of source code must retain the above copyright
9 | * notice, this list of conditions and the following disclaimer.
10 | * * Redistributions in binary form must reproduce the above copyright
11 | * notice, this list of conditions and the following disclaimer in the
12 | * documentation and/or other materials provided with the distribution.
13 | * * Neither the name of the University of California, Berkeley nor the
14 | * names of its contributors may be used to endorse or promote products
15 | * derived from this software without specific prior written permission.
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
18 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
21 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 | */
28 |
29 | /* CRC16 implementation according to CCITT standards.
30 | *
31 | * Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the
32 | * following parameters:
33 | *
34 | * Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN"
35 | * Width : 16 bit
36 | * Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1)
37 | * Initialization : 0000
38 | * Reflect Input byte : False
39 | * Reflect Output CRC : False
40 | * Xor constant to output CRC : 0000
41 | * Output for "123456789" : 31C3
42 | */
43 |
44 | #include
45 |
46 | static const uint16_t crc16tab[256]= {
47 | 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
48 | 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
49 | 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
50 | 0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
51 | 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
52 | 0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
53 | 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
54 | 0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
55 | 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
56 | 0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
57 | 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
58 | 0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
59 | 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
60 | 0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
61 | 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
62 | 0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
63 | 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
64 | 0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
65 | 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
66 | 0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
67 | 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
68 | 0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
69 | 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
70 | 0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
71 | 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
72 | 0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
73 | 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
74 | 0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
75 | 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
76 | 0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
77 | 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
78 | 0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
79 | };
80 |
81 | static uint16_t crc16(const char *buf, int len) {
82 | int counter;
83 | uint16_t crc = 0;
84 | for (counter = 0; counter < len; counter++)
85 | crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF];
86 | return crc;
87 | }
88 |
--------------------------------------------------------------------------------
/debian.control:
--------------------------------------------------------------------------------
1 | Package: phpredis
2 | Version: 2.2.4
3 | Section: web
4 | Priority: optional
5 | Architecture: all
6 | Essential: no
7 | Depends:
8 | Pre-Depends:
9 | Recommends: php5
10 | Suggests:
11 | Installed-Size:
12 | Maintainer: Nicolas Favre-Felix [n.favre-felix@owlient.eu]
13 | Conflicts:
14 | Replaces:
15 | Provides: phpredis
16 | Description: Redis C extension for PHP5.
17 |
--------------------------------------------------------------------------------
/debian/changelog:
--------------------------------------------------------------------------------
1 | php5-redis (2.2.2-1) unstable; urgency=low
2 |
3 | * Non-maintainer upload.
4 | * Updating version from upstream
5 |
6 | -- Rémi Paulmier Fri, 02 Nov 2012 10:30:28 +0100
7 |
8 | php5-redis (2.1.3-1) unstable; urgency=low
9 |
10 | * Non-maintainer upload.
11 | * changing package name, updating version from upstream
12 |
13 | -- Roman Ovchinnikov Mon, 02 Apr 2012 20:43:25 +0400
14 |
15 | phpredis (2.0.11) unstable; urgency=low
16 |
17 | * Merged with upstream
18 |
19 | -- Simon Effenberg Tue, 30 Nov 2010 09:20:40 +0100
20 |
21 | phpredis (2.0.10-1) unstable; urgency=low
22 |
23 | * Fixed wrong timeout option when setting timeout to lower then 1s.
24 |
25 | -- Simon Effenberg Mon, 08 Nov 2010 12:30:54 +0100
26 |
27 | phpredis (2.0.10) unstable; urgency=low
28 |
29 | * Merged with upstream
30 |
31 | -- Simon Effenberg Fri, 05 Nov 2010 16:07:09 +0100
32 |
33 | phpredis (2.0.8-2) unstable; urgency=low
34 |
35 | * Fixed problem when doing incrBy or decrBy with 1 like incrBy('key', 1).
36 |
37 | -- Simon Effenberg Wed, 27 Oct 2010 13:33:18 +0200
38 |
39 | phpredis (2.0.8-1) unstable; urgency=low
40 |
41 | * Merged with upstream but there seems to be no new version.
42 | * incrBy/decrBy added
43 |
44 | -- Simon Effenberg Wed, 27 Oct 2010 10:36:35 +0200
45 |
46 | phpredis (2.0.8) unstable; urgency=low
47 |
48 | * Merged with upstream
49 |
50 | -- Simon Effenberg Fri, 15 Oct 2010 13:00:32 +0200
51 |
52 | phpredis (2.0.4) unstable; urgency=low
53 |
54 | * Merged with upstream
55 |
56 | -- Simon Effenberg Tue, 05 Oct 2010 09:47:48 +0200
57 |
58 | phpredis (2.0.2) unstable; urgency=low
59 |
60 | * Fixed version number
61 |
62 | -- Nicolas Favre-Felix Thu, 23 Sep 2010 14:28:00 +0100
63 |
64 | phpredis (1.0.0-2) unstable; urgency=low
65 |
66 | * Fix some little debian/* problems.
67 |
68 | -- Simon Effenberg Thu, 23 Sep 2010 11:50:59 +0200
69 |
70 | phpredis (1.0.0-1) unstable; urgency=low
71 |
72 | * Initial release.
73 |
74 | -- Simon Effenberg Wed, 22 Sep 2010 16:04:53 +0200
75 |
--------------------------------------------------------------------------------
/debian/compat:
--------------------------------------------------------------------------------
1 | 7
2 |
--------------------------------------------------------------------------------
/debian/control:
--------------------------------------------------------------------------------
1 | Source: php5-redis
2 | Section: web
3 | Priority: optional
4 | Maintainer: Nicolas Favre-Felix
5 | Build-Depends: debhelper (>= 7), php5-dev
6 | Standards-Version: 3.8.0
7 | Homepage: https://github.com/nicolasff/phpredis
8 |
9 | Package: php5-redis
10 | Architecture: any
11 | Depends: ${shlibs:Depends}, ${misc:Depends}, ${php5:Depends}
12 | Recommends: php5
13 | Provides: php5-redis
14 | Conflicts: phpredis
15 | Replaces: phpredis
16 | Description: Redis C extension for PHP5.
17 |
--------------------------------------------------------------------------------
/debian/copyright:
--------------------------------------------------------------------------------
1 | This package was debianized by:
2 |
3 | Simon Effenberg on Mon, 22 Sep 2010 14:24:03 +0100
4 |
5 | It was downloaded from:
6 |
7 | http://github.com/owlient/phpredis
8 |
9 | Upstream Author(s):
10 |
11 | Nicolas Favre-Felix
12 | Nasreddine Bouafif
13 |
14 | Copyright:
15 |
16 |
17 |
18 |
19 | License:
20 |
21 | ### SELECT: ###
22 | This package is free software; you can redistribute it and/or modify
23 | it under the terms of the GNU General Public License as published by
24 | the Free Software Foundation; either version 2 of the License, or
25 | (at your option) any later version.
26 | ### OR ###
27 | This package is free software; you can redistribute it and/or modify
28 | it under the terms of the GNU General Public License version 2 as
29 | published by the Free Software Foundation.
30 | ##########
31 |
32 | This package is distributed in the hope that it will be useful,
33 | but WITHOUT ANY WARRANTY; without even the implied warranty of
34 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35 | GNU General Public License for more details.
36 |
37 | You should have received a copy of the GNU General Public License
38 | along with this package; if not, write to the Free Software
39 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
40 |
41 | On Debian systems, the complete text of the GNU General
42 | Public License can be found in `/usr/share/common-licenses/GPL'.
43 |
44 | The Debian packaging is:
45 |
46 | Copyright C) 2010, Simon Effenberg
47 |
48 | and is licensed under the GPL, see above.
49 |
50 |
51 | # Please also look if there are files or directories which have a
52 | # different copyright/license attached and list them here.
53 |
54 |
--------------------------------------------------------------------------------
/debian/postinst:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # postinst script for phpredis
3 | #
4 | # see: dh_installdeb(1)
5 |
6 | set -e
7 |
8 | # summary of how this script can be called:
9 | # * `configure'
10 | # * `abort-upgrade'
11 | # * `abort-remove' `in-favour'
12 | #
13 | # * `abort-remove'
14 | # * `abort-deconfigure' `in-favour'
15 | # `removing'
16 | #
17 | # for details, see http://www.debian.org/doc/debian-policy/ or
18 | # the debian-policy package
19 |
20 | case "$1" in
21 | configure)
22 | cat > /etc/php5/conf.d/redis.ini <&2
33 | exit 1
34 | ;;
35 | esac
36 |
37 | # dh_installdeb will replace this with shell code automatically
38 | # generated by other debhelper scripts.
39 |
40 | #DEBHELPER#
41 |
42 | exit 0
43 |
--------------------------------------------------------------------------------
/debian/postrm:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # postinst script for an Apache virtual host component of plista
3 | #
4 | # see: dh_installdeb(1)
5 |
6 | set -e
7 |
8 | # summary of how this script can be called:
9 | # * `remove'
10 | # * `purge'
11 | # * `upgrade'
12 | # * `failed-upgrade'
13 | # * `abort-install'
14 | # * `abort-install'
15 | # * `abort-upgrade'
16 | # * `disappear'
17 | #
18 | # for details, see http://www.debian.org/doc/debian-policy/ or
19 | # the debian-policy package
20 |
21 | case "$1" in
22 | purge)
23 | if [ -e /etc/php5/conf.d/redis.ini ]; then
24 | rm -f /etc/php5/conf.d/redis.ini
25 | fi
26 | ;;
27 | remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
28 | ;;
29 |
30 | *)
31 | echo "postrm called with unknown argument \`$1'" >&2
32 | exit 1
33 | ;;
34 | esac
35 |
36 | # dh_installdeb will replace this with shell code automatically
37 | # generated by other debhelper scripts.
38 |
39 | #DEBHELPER#
40 |
41 | exit 0
42 |
--------------------------------------------------------------------------------
/debian/rules:
--------------------------------------------------------------------------------
1 | #!/usr/bin/make -f
2 | # Sample debian/rules that uses debhelper.
3 | # This file is public domain software, originally written by Joey Hess.
4 | #
5 | # This version is for packages that are architecture dependent.
6 |
7 | # Uncomment this to turn on verbose mode.
8 | #export DH_VERBOSE=1
9 |
10 | DEB_SRCDIR=$(shell pwd)
11 | INSTALL_DIR=$(DEB_SRCDIR)/debian/$(shell dh_listpackages)
12 | EXTENSION_DIR=`php-config5 --extension-dir`
13 | CFLAGS = -O3
14 |
15 |
16 | build: build-stamp
17 | build-stamp:
18 | dh_testdir
19 |
20 | # Add here commands to compile the package.
21 |
22 | # Because phpize --clean removes all testfiles
23 | # in tests/*.php the svn-buildpackage will fail
24 | # when tests/TestRedis.php was removed.
25 | # So we backup the file:
26 | cd $(DEB_SRCDIR) && mv tests/TestRedis.php tests/TestRedis.php.bak && \
27 | phpize --clean && mv tests/TestRedis.php.bak tests/TestRedis.php && \
28 | phpize && \
29 | ./configure --with-php-config=/usr/bin/php-config5
30 | $(MAKE) -C $(DEB_SRCDIR)
31 |
32 | touch build-stamp
33 |
34 | clean:
35 | dh_testdir
36 | dh_testroot
37 | rm -f build-stamp
38 |
39 | # Add here commands to clean up after the build process.
40 | # See comment in build-stamp why doing the 'mv'
41 | cd $(DEB_SRCDIR) && mv tests/TestRedis.php tests/TestRedis.php.bak && \
42 | phpize --clean && mv tests/TestRedis.php.bak tests/TestRedis.php
43 | #$(MAKE) -C $(DEB_SRCDIR) clean
44 | #$(MAKE) distclean
45 |
46 | dh_clean
47 |
48 | install: build
49 | dh_testdir
50 | dh_testroot
51 | dh_prep
52 | dh_installdirs
53 |
54 | # Add here commands to install the package into debian/
55 | $(MAKE) prefix=$(INSTALL_DIR)/usr EXTENSION_DIR=$(INSTALL_DIR)$(EXTENSION_DIR) install
56 |
57 | # Build architecture-independent files here.
58 | binary-indep: build install
59 | # We have nothing to do by default.
60 |
61 | # Build architecture-dependent files here.
62 | binary-arch: build install
63 | dh_testdir
64 | dh_testroot
65 | dh_installchangelogs
66 | dh_installdocs
67 | dh_installexamples
68 | # dh_install
69 | # dh_installmenu
70 | # dh_installdebconf
71 | # dh_installlogrotate
72 | # dh_installemacsen
73 | # dh_installcatalogs
74 | # dh_installpam
75 | # dh_installmime
76 | # dh_installinit
77 | # dh_installcron
78 | # dh_installinfo
79 | # dh_installwm
80 | # dh_installudev
81 | # dh_lintian
82 | # dh_bugfiles
83 | # dh_undocumented
84 | dh_installman
85 | dh_link
86 | dh_strip
87 | dh_compress
88 | dh_fixperms
89 | # dh_perl
90 | # dh_makeshlibs
91 | dh_installdeb
92 | dh_shlibdeps
93 | cd $(DEB_SRCDIR) && \
94 | echo "php5:Depends=phpapi-`php-config5 --phpapi`, php5-common" >> debian/phpredis.substvars
95 | dh_gencontrol
96 | dh_md5sums
97 | dh_builddeb
98 |
99 | binary: binary-indep binary-arch
100 | .PHONY: build clean binary-indep binary-arch binary install
101 |
--------------------------------------------------------------------------------
/library.h:
--------------------------------------------------------------------------------
1 | #ifndef REDIS_LIBRARY_H
2 | #define REDIS_LIBRARY_H
3 |
4 | void add_constant_long(zend_class_entry *ce, char *name, int value);
5 | int integer_length(int i);
6 | int redis_cmd_format(char **ret, char *format, ...);
7 | int redis_cmd_format_static(char **ret, char *keyword, char *format, ...);
8 | int redis_cmd_format_header(char **ret, char *keyword, int arg_count);
9 | int redis_cmd_append_str(char **cmd, int cmd_len, char *append, int append_len);
10 | int redis_cmd_init_sstr(smart_str *str, int num_args, char *keyword, int keyword_len);
11 | int redis_cmd_append_sstr(smart_str *str, char *append, int append_len);
12 | int redis_cmd_append_sstr_int(smart_str *str, int append);
13 | int redis_cmd_append_sstr_long(smart_str *str, long append);
14 | int redis_cmd_append_int(char **cmd, int cmd_len, int append);
15 | int redis_cmd_append_sstr_dbl(smart_str *str, double value);
16 |
17 | PHP_REDIS_API char * redis_sock_read(RedisSock *redis_sock, size_t *buf_len);
18 | PHP_REDIS_API int redis_sock_gets(RedisSock *redis_sock, char *buf, int buf_size, size_t* line_len);
19 | PHP_REDIS_API void redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
20 | PHP_REDIS_API void redis_long_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval* z_tab, void *ctx);
21 | typedef void (*SuccessCallback)(RedisSock *redis_sock);
22 | PHP_REDIS_API void redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx, SuccessCallback success_callback);
23 | PHP_REDIS_API void redis_boolean_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
24 | PHP_REDIS_API void redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
25 | PHP_REDIS_API void redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
26 | PHP_REDIS_API void redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
27 | PHP_REDIS_API void redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
28 | PHP_REDIS_API zval *redis_parse_info_response(char *resp, zval *rv);
29 | PHP_REDIS_API zval *redis_parse_client_list_response(char *resp, zval *rv);
30 | PHP_REDIS_API void redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
31 | PHP_REDIS_API RedisSock* redis_sock_create(char *host, int host_len, unsigned short port, double timeout, int persistent, char *persistent_id, long retry_interval, zend_bool lazy_connect);
32 | PHP_REDIS_API int redis_sock_connect(RedisSock *redis_sock);
33 | PHP_REDIS_API int redis_sock_server_open(RedisSock *redis_sock, int force_connect);
34 | PHP_REDIS_API int redis_sock_disconnect(RedisSock *redis_sock);
35 | PHP_REDIS_API zval *redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *rv);
36 | PHP_REDIS_API char *redis_sock_read_bulk_reply(RedisSock *redis_sock, int bytes);
37 | PHP_REDIS_API int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *_z_tab, void *ctx);
38 | PHP_REDIS_API void redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int count, int unserialize);
39 |
40 | PHP_REDIS_API int redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
41 | PHP_REDIS_API int redis_mbulk_reply_zipped_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
42 | PHP_REDIS_API int redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
43 | PHP_REDIS_API int redis_mbulk_reply_zipped_keys_int(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
44 | PHP_REDIS_API int redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
45 | PHP_REDIS_API int redis_mbulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
46 |
47 | PHP_REDIS_API int redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, REDIS_SCAN_TYPE type, long *iter);
48 |
49 | PHP_REDIS_API int redis_subscribe_response(INTERNAL_FUNCTION_PARAMETERS,
50 | RedisSock *redis_sock, zval *z_tab, void *ctx);
51 | PHP_REDIS_API int redis_unsubscribe_response(INTERNAL_FUNCTION_PARAMETERS,
52 | RedisSock *redis_sock, zval *z_tab, void *ctx);
53 |
54 | PHP_REDIS_API int redis_sock_write(RedisSock *redis_sock, char *cmd, size_t sz);
55 | PHP_REDIS_API void redis_stream_close(RedisSock *redis_sock);
56 | PHP_REDIS_API int redis_check_eof(RedisSock *redis_sock, int no_throw);
57 | PHP_REDIS_API int redis_sock_get(zval *id, RedisSock **redis_sock, int nothrow);
58 | PHP_REDIS_API void redis_free_socket(RedisSock *redis_sock);
59 | PHP_REDIS_API void redis_send_discard(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock);
60 | PHP_REDIS_API int redis_sock_set_err(RedisSock *redis_sock, const char *msg, int msg_len);
61 |
62 | PHP_REDIS_API zend_string *
63 | redis_serialize(RedisSock *redis_sock, zval *z);
64 | PHP_REDIS_API zend_string *
65 | redis_key_prefix(RedisSock *redis_sock, zend_string *key);
66 |
67 | PHP_REDIS_API int
68 | redis_unserialize(RedisSock *redis_sock, const char *val, int val_len, zval *return_value);
69 |
70 |
71 | /*
72 | * Variant Read methods, mostly to implement eval
73 | */
74 |
75 | PHP_REDIS_API int redis_read_reply_type(RedisSock *redis_sock, REDIS_REPLY_TYPE *reply_type, int *reply_info);
76 | PHP_REDIS_API int redis_read_variant_line(RedisSock *redis_sock, REDIS_REPLY_TYPE reply_type, zval *z_ret);
77 | PHP_REDIS_API int redis_read_variant_bulk(RedisSock *redis_sock, int size, zval *z_ret);
78 | PHP_REDIS_API int redis_read_multibulk_recursive(RedisSock *redis_sock, int elements, zval *z_ret);
79 | PHP_REDIS_API int redis_read_variant_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
80 |
81 | PHP_REDIS_API void redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab);
82 |
83 | #define REDIS_DOUBLE_TO_STRING(dbl_str, dbl) \
84 | char dbl_decsep; \
85 | dbl_decsep = '.'; \
86 | dbl_str = _php_math_number_format_ex(dbl, 16, &dbl_decsep, 1, NULL, 0); \
87 |
88 |
89 | #endif
90 |
--------------------------------------------------------------------------------
/mkdeb-apache2.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | phpize
3 | ./configure CFLAGS="-O3"
4 | make clean all
5 | DIR=`php-config --extension-dir | cut -c 2-`
6 |
7 | rm -rf debian
8 |
9 | mkdir -p debian
10 | mkdir -p debian/DEBIAN
11 | mkdir -p debian/$DIR
12 |
13 | cp debian.control debian/DEBIAN/control
14 |
15 | UBUNTU=`uname -v | grep -ci ubuntu`
16 | mkdir -p debian/etc/php5/apache2/conf.d/
17 | if [ $UBUNTU = "0" ]; then
18 | mkdir -p debian/etc/php5/cli/conf.d/
19 | fi
20 |
21 | echo "extension=redis.so" >> debian/etc/php5/apache2/conf.d/redis.ini
22 |
23 | if [ $UBUNTU = "0" ]; then
24 | cp debian/etc/php5/apache2/conf.d/redis.ini debian/etc/php5/cli/conf.d/redis.ini
25 | fi
26 |
27 | cp modules/redis.so debian/$DIR
28 | dpkg -b debian phpredis-`uname -m`.deb
29 | rm -rf debian/
30 |
--------------------------------------------------------------------------------
/mkdeb.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | phpize
3 | ./configure CFLAGS="-O3"
4 | make clean all
5 | DIR=`php-config --extension-dir | cut -c 2-`
6 |
7 | rm -rf debian
8 |
9 | mkdir -p debian
10 | mkdir -p debian/DEBIAN
11 | mkdir -p debian/$DIR
12 |
13 | cp debian.control debian/DEBIAN/control
14 |
15 | UBUNTU=`uname -v | grep -ci ubuntu`
16 | mkdir -p debian/etc/php5/conf.d/
17 | echo "extension=redis.so" >> debian/etc/php5/conf.d/redis.ini
18 |
19 | cp modules/redis.so debian/$DIR
20 | dpkg -b debian phpredis-`git describe --abbrev=0 --tags`_`uname -m`.deb
21 | rm -rf debian/
22 |
--------------------------------------------------------------------------------
/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 | redis
7 | pecl.php.net
8 | PHP extension for interfacing with Redis
9 |
10 | This extension provides an API for communicating with Redis servers.
11 |
12 |
13 | Nicolas Favre-Felix
14 | nff
15 | n.favrefelix@gmail.com
16 | yes
17 |
18 |
19 | Michael Grunder
20 | mgrunder
21 | michael.grunder@gmail.com
22 | yes
23 |
24 | 2014-03-15
25 |
26 | 2.2.5
27 | 2.2.5
28 |
29 |
30 | stable
31 | stable
32 |
33 | PHP
34 |
35 | phpredis 2.2.5
36 |
37 | This is a minor release with several bug fixes as well as additions to support
38 | new commands that have been introduced to Redis since our last release.
39 |
40 | A special thanks to everyone who helps the project by commenting on issues and
41 | submitting pull requests! :)
42 |
43 | [NEW] Support for the BITPOS command
44 | [NEW] Connection timeout option for RedisArray (@MikeToString)
45 | [NEW] A _serialize method, to complement our existing _unserialize method
46 | [NEW] Support for the PUBSUB command
47 | [NEW] Support for SCAN, SSCAN, HSCAN, and ZSCAN
48 | [NEW] Support for the WAIT command
49 |
50 | [FIX] Handle the COPY and REPLACE arguments for the MIGRATE command
51 |
52 | [DOC] Fix syntax error in documentation for the SET command (@mithunsatheesh)
53 | [DOC] Homebrew documentation instructions (@mathias)
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | 5.2.0
87 | 6.0.0
88 | 6.0.0
89 |
90 |
91 | 1.4.0b1
92 |
93 |
94 |
95 | redis
96 |
97 |
98 |
99 | stablestable
100 | 2.2.52.2.5
101 | 2014-03-15
102 |
103 | phpredis 2.2.5
104 |
105 | This is a minor release with several bug fixes as well as additions to support
106 | new commands that have been introduced to Redis since our last release.
107 |
108 | A special thanks to everyone who helps the project by commenting on issues and
109 | submitting pull requests! :)
110 |
111 | [NEW] Support for the BITPOS command
112 | [NEW] Connection timeout option for RedisArray (@MikeToString)
113 | [NEW] A _serialize method, to complement our existing _unserialize method
114 | [NEW] Support for the PUBSUB command
115 | [NEW] Support for SCAN, SSCAN, HSCAN, and ZSCAN
116 | [NEW] Support for the WAIT command
117 |
118 | [FIX] Handle the COPY and REPLACE arguments for the MIGRATE command
119 |
120 | [DOC] Fix syntax error in documentation for the SET command (@mithunsatheesh)
121 | [DOC] Homebrew documentation instructions (@mathias)
122 |
123 |
124 |
125 |
126 | stablestable
127 | 2.2.42.2.4
128 | 2013-09-01
129 |
130 | **
131 | ** Features / Improvements
132 | **
133 |
134 | * Randomized reconnect delay for RedisArray @mobli
135 | This feature adds an optional parameter when constructing a RedisArray object
136 | such that a random delay will be introduced if reconnections are made,
137 | mitigating any 'thundering herd' type problems.
138 |
139 | * Lazy connections to RedisArray servers @mobli
140 | By default, RedisArray will attempt to connect to each server you pass in
141 | the ring on construction. This feature lets you specify that you would
142 | rather have RedisArray only attempt a connection when it needs to get data
143 | from a particular node (throughput/performance improvement).
144 |
145 | * Allow LONG and STRING keys in MGET/MSET
146 | * Extended SET options for Redis >= 2.6.12
147 | * Persistent connections and UNIX SOCKET support for RedisArray
148 | * Allow aggregates for ZUNION/ZINTER without weights @mheijkoop
149 | * Support for SLOWLOG command
150 | * Reworked MGET algorithm to run in linear time regardless of key count.
151 | * Reworked ZINTERSTORE/ZUNIONSTORE algorithm to run in linear time
152 |
153 | **
154 | ** Bug fixes
155 | **
156 |
157 | * C99 Compliance (or rather lack thereof) fix @mobli
158 | * Added ZEND_ACC_CTOR and ZEND_ACC_DTOR @euskadi31
159 | * Stop throwing and clearing an exception on connect failure @matmoi
160 | * Fix a false positive unit test failure having to do with TTL returns
161 |
162 |
163 |
164 | stablestable
165 | 2.2.32.2.3
166 | 2013-04-29
167 |
168 | First release to PECL
169 |
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/php_redis.h:
--------------------------------------------------------------------------------
1 | /*
2 | +----------------------------------------------------------------------+
3 | | PHP Version 5 |
4 | +----------------------------------------------------------------------+
5 | | Copyright (c) 1997-2009 The PHP Group |
6 | +----------------------------------------------------------------------+
7 | | This source file is subject to version 3.01 of the PHP license, |
8 | | that is bundled with this package in the file LICENSE, and is |
9 | | available through the world-wide-web at the following url: |
10 | | http://www.php.net/license/3_01.txt |
11 | | If you did not receive a copy of the PHP license and are unable to |
12 | | obtain it through the world-wide-web, please send a note to |
13 | | license@php.net so we can mail you a copy immediately. |
14 | +----------------------------------------------------------------------+
15 | | Original author: Alfonso Jimenez |
16 | | Maintainer: Nicolas Favre-Felix |
17 | | Maintainer: Michael Grunder |
18 | | Maintainer: Nasreddine Bouafif |
19 | +----------------------------------------------------------------------+
20 | */
21 |
22 | #include "common.h"
23 |
24 | #ifndef PHP_REDIS_H
25 | #define PHP_REDIS_H
26 |
27 | /* phpredis version */
28 | #define PHP_REDIS_VERSION "2.2.5"
29 |
30 | PHP_METHOD(Redis, __construct);
31 | PHP_METHOD(Redis, __destruct);
32 | PHP_METHOD(Redis, connect);
33 | PHP_METHOD(Redis, pconnect);
34 | PHP_METHOD(Redis, close);
35 | PHP_METHOD(Redis, ping);
36 | PHP_METHOD(Redis, echo);
37 | PHP_METHOD(Redis, get);
38 | PHP_METHOD(Redis, set);
39 | PHP_METHOD(Redis, setex);
40 | PHP_METHOD(Redis, psetex);
41 | PHP_METHOD(Redis, setnx);
42 | PHP_METHOD(Redis, getSet);
43 | PHP_METHOD(Redis, randomKey);
44 | PHP_METHOD(Redis, renameKey);
45 | PHP_METHOD(Redis, renameNx);
46 | PHP_METHOD(Redis, getMultiple);
47 | PHP_METHOD(Redis, exists);
48 | PHP_METHOD(Redis, delete);
49 | PHP_METHOD(Redis, incr);
50 | PHP_METHOD(Redis, incrBy);
51 | PHP_METHOD(Redis, incrByFloat);
52 | PHP_METHOD(Redis, decr);
53 | PHP_METHOD(Redis, decrBy);
54 | PHP_METHOD(Redis, type);
55 | PHP_METHOD(Redis, append);
56 | PHP_METHOD(Redis, getRange);
57 | PHP_METHOD(Redis, setRange);
58 | PHP_METHOD(Redis, getBit);
59 | PHP_METHOD(Redis, setBit);
60 | PHP_METHOD(Redis, strlen);
61 | PHP_METHOD(Redis, getKeys);
62 | PHP_METHOD(Redis, sort);
63 | PHP_METHOD(Redis, sortAsc);
64 | PHP_METHOD(Redis, sortAscAlpha);
65 | PHP_METHOD(Redis, sortDesc);
66 | PHP_METHOD(Redis, sortDescAlpha);
67 | PHP_METHOD(Redis, lPush);
68 | PHP_METHOD(Redis, lPushx);
69 | PHP_METHOD(Redis, rPush);
70 | PHP_METHOD(Redis, rPushx);
71 | PHP_METHOD(Redis, lPop);
72 | PHP_METHOD(Redis, rPop);
73 | PHP_METHOD(Redis, blPop);
74 | PHP_METHOD(Redis, brPop);
75 | PHP_METHOD(Redis, lSize);
76 | PHP_METHOD(Redis, lRemove);
77 | PHP_METHOD(Redis, listTrim);
78 | PHP_METHOD(Redis, lGet);
79 | PHP_METHOD(Redis, lGetRange);
80 | PHP_METHOD(Redis, lSet);
81 | PHP_METHOD(Redis, lInsert);
82 | PHP_METHOD(Redis, sAdd);
83 | PHP_METHOD(Redis, sSize);
84 | PHP_METHOD(Redis, sRemove);
85 | PHP_METHOD(Redis, sMove);
86 | PHP_METHOD(Redis, sPop);
87 | PHP_METHOD(Redis, sRandMember);
88 | PHP_METHOD(Redis, sContains);
89 | PHP_METHOD(Redis, sMembers);
90 | PHP_METHOD(Redis, sInter);
91 | PHP_METHOD(Redis, sInterStore);
92 | PHP_METHOD(Redis, sUnion);
93 | PHP_METHOD(Redis, sUnionStore);
94 | PHP_METHOD(Redis, sDiff);
95 | PHP_METHOD(Redis, sDiffStore);
96 | PHP_METHOD(Redis, setTimeout);
97 | PHP_METHOD(Redis, pexpire);
98 | PHP_METHOD(Redis, save);
99 | PHP_METHOD(Redis, bgSave);
100 | PHP_METHOD(Redis, lastSave);
101 | PHP_METHOD(Redis, flushDB);
102 | PHP_METHOD(Redis, flushAll);
103 | PHP_METHOD(Redis, dbSize);
104 | PHP_METHOD(Redis, auth);
105 | PHP_METHOD(Redis, ttl);
106 | PHP_METHOD(Redis, pttl);
107 | PHP_METHOD(Redis, persist);
108 | PHP_METHOD(Redis, info);
109 | PHP_METHOD(Redis, select);
110 | PHP_METHOD(Redis, move);
111 | PHP_METHOD(Redis, zAdd);
112 | PHP_METHOD(Redis, zDelete);
113 | PHP_METHOD(Redis, zRange);
114 | PHP_METHOD(Redis, zRevRange);
115 | PHP_METHOD(Redis, zRangeByScore);
116 | PHP_METHOD(Redis, zRevRangeByScore);
117 | PHP_METHOD(Redis, zRangeByLex);
118 | PHP_METHOD(Redis, zRevRangeByLex);
119 | PHP_METHOD(Redis, zRemRangeByLex);
120 | PHP_METHOD(Redis, zLexCount);
121 | PHP_METHOD(Redis, zCount);
122 | PHP_METHOD(Redis, zDeleteRangeByScore);
123 | PHP_METHOD(Redis, zDeleteRangeByRank);
124 | PHP_METHOD(Redis, zCard);
125 | PHP_METHOD(Redis, zScore);
126 | PHP_METHOD(Redis, zRank);
127 | PHP_METHOD(Redis, zRevRank);
128 | PHP_METHOD(Redis, zIncrBy);
129 | PHP_METHOD(Redis, zInter);
130 | PHP_METHOD(Redis, zUnion);
131 | PHP_METHOD(Redis, expireAt);
132 | PHP_METHOD(Redis, pexpireAt);
133 | PHP_METHOD(Redis, bgrewriteaof);
134 | PHP_METHOD(Redis, slaveof);
135 | PHP_METHOD(Redis, object);
136 | PHP_METHOD(Redis, bitop);
137 | PHP_METHOD(Redis, bitcount);
138 | PHP_METHOD(Redis, bitpos);
139 |
140 | PHP_METHOD(Redis, eval);
141 | PHP_METHOD(Redis, evalsha);
142 | PHP_METHOD(Redis, script);
143 | PHP_METHOD(Redis, dump);
144 | PHP_METHOD(Redis, restore);
145 | PHP_METHOD(Redis, migrate);
146 |
147 | PHP_METHOD(Redis, time);
148 | PHP_METHOD(Redis, role);
149 |
150 | PHP_METHOD(Redis, getLastError);
151 | PHP_METHOD(Redis, clearLastError);
152 | PHP_METHOD(Redis, _prefix);
153 | PHP_METHOD(Redis, _serialize);
154 | PHP_METHOD(Redis, _unserialize);
155 |
156 | PHP_METHOD(Redis, mset);
157 | PHP_METHOD(Redis, msetnx);
158 | PHP_METHOD(Redis, rpoplpush);
159 | PHP_METHOD(Redis, brpoplpush);
160 |
161 | PHP_METHOD(Redis, hGet);
162 | PHP_METHOD(Redis, hSet);
163 | PHP_METHOD(Redis, hSetNx);
164 | PHP_METHOD(Redis, hDel);
165 | PHP_METHOD(Redis, hLen);
166 | PHP_METHOD(Redis, hKeys);
167 | PHP_METHOD(Redis, hVals);
168 | PHP_METHOD(Redis, hGetAll);
169 | PHP_METHOD(Redis, hExists);
170 | PHP_METHOD(Redis, hIncrBy);
171 | PHP_METHOD(Redis, hIncrByFloat);
172 | PHP_METHOD(Redis, hMset);
173 | PHP_METHOD(Redis, hMget);
174 |
175 | PHP_METHOD(Redis, multi);
176 | PHP_METHOD(Redis, discard);
177 | PHP_METHOD(Redis, exec);
178 | PHP_METHOD(Redis, watch);
179 | PHP_METHOD(Redis, unwatch);
180 |
181 | PHP_METHOD(Redis, pipeline);
182 |
183 | PHP_METHOD(Redis, publish);
184 | PHP_METHOD(Redis, subscribe);
185 | PHP_METHOD(Redis, psubscribe);
186 | PHP_METHOD(Redis, unsubscribe);
187 | PHP_METHOD(Redis, punsubscribe);
188 |
189 | PHP_METHOD(Redis, getOption);
190 | PHP_METHOD(Redis, setOption);
191 |
192 | PHP_METHOD(Redis, config);
193 | PHP_METHOD(Redis, slowlog);
194 | PHP_METHOD(Redis, wait);
195 | PHP_METHOD(Redis, pubsub);
196 |
197 | PHP_METHOD(Redis, client);
198 | PHP_METHOD(Redis, rawcommand);
199 |
200 | /* SCAN and friends */
201 | PHP_METHOD(Redis, scan);
202 | PHP_METHOD(Redis, hscan);
203 | PHP_METHOD(Redis, sscan);
204 | PHP_METHOD(Redis, zscan);
205 |
206 | /* HyperLogLog commands */
207 | PHP_METHOD(Redis, pfadd);
208 | PHP_METHOD(Redis, pfcount);
209 | PHP_METHOD(Redis, pfmerge);
210 |
211 | /* Reflection */
212 | PHP_METHOD(Redis, getHost);
213 | PHP_METHOD(Redis, getPort);
214 | PHP_METHOD(Redis, getDBNum);
215 | PHP_METHOD(Redis, getTimeout);
216 | PHP_METHOD(Redis, getReadTimeout);
217 | PHP_METHOD(Redis, isConnected);
218 | PHP_METHOD(Redis, getPersistentID);
219 | PHP_METHOD(Redis, getAuth);
220 | PHP_METHOD(Redis, getMode);
221 | PHP_METHOD(Redis, rawcommand);
222 |
223 | #ifdef ZTS
224 | #include "TSRM.h"
225 | #endif
226 |
227 | PHP_MINIT_FUNCTION(redis);
228 | PHP_MSHUTDOWN_FUNCTION(redis);
229 | PHP_RINIT_FUNCTION(redis);
230 | PHP_RSHUTDOWN_FUNCTION(redis);
231 | PHP_MINFO_FUNCTION(redis);
232 |
233 | /* Redis response handler function callback prototype */
234 | typedef void (*ResultCallback)(INTERNAL_FUNCTION_PARAMETERS,
235 | RedisSock *redis_sock, zval *z_tab, void *ctx);
236 |
237 | PHP_REDIS_API int redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent);
238 |
239 | PHP_REDIS_API void generic_sort_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sort,
240 | int use_alpha);
241 |
242 | PHP_REDIS_API void generic_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sub_cmd);
243 |
244 | PHP_REDIS_API void generic_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS,
245 | char *unsub_cmd);
246 |
247 | PHP_REDIS_API int redis_response_enqueued(RedisSock *redis_sock);
248 |
249 | PHP_REDIS_API int get_flag(zval *object);
250 |
251 | PHP_REDIS_API void set_flag(zval *object, int new_flag);
252 |
253 | PHP_REDIS_API int redis_sock_read_multibulk_multi_reply_loop(
254 | INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab,
255 | int numElems);
256 |
257 | /* pipeline */
258 | PHP_REDIS_API request_item* get_pipeline_head(zval *object);
259 | PHP_REDIS_API void set_pipeline_head(zval *object, request_item *head);
260 | PHP_REDIS_API request_item* get_pipeline_current(zval *object);
261 | PHP_REDIS_API void set_pipeline_current(zval *object, request_item *current);
262 |
263 | #ifndef _MSC_VER
264 | ZEND_BEGIN_MODULE_GLOBALS(redis)
265 | ZEND_END_MODULE_GLOBALS(redis)
266 | #endif
267 |
268 | struct redis_queued_item {
269 | /* reading function */
270 | zval * (*fun)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, ...);
271 |
272 | char *cmd;
273 | int cmd_len;
274 |
275 | struct redis_queued_item *next;
276 | };
277 |
278 | extern zend_module_entry redis_module_entry;
279 |
280 | #define redis_module_ptr &redis_module_entry
281 | #define phpext_redis_ptr redis_module_ptr
282 |
283 | #endif
284 |
285 | /*
286 | * Local variables:
287 | * tab-width: 4
288 | * c-basic-offset: 4
289 | * End:
290 | * vim600: noet sw=4 ts=4 fdm=marker
291 | * vim<600: noet sw=4 ts=4
292 | */
293 |
--------------------------------------------------------------------------------
/redis_array.h:
--------------------------------------------------------------------------------
1 | #ifndef REDIS_ARRAY_H
2 | #define REDIS_ARRAY_H
3 |
4 | #include
5 | #include "common.h"
6 |
7 | void redis_destructor_redis_array(zend_resource * rsrc);
8 |
9 | PHP_METHOD(RedisArray, __construct);
10 | PHP_METHOD(RedisArray, __call);
11 | PHP_METHOD(RedisArray, _hosts);
12 | PHP_METHOD(RedisArray, _target);
13 | PHP_METHOD(RedisArray, _instance);
14 | PHP_METHOD(RedisArray, _function);
15 | PHP_METHOD(RedisArray, _distributor);
16 | PHP_METHOD(RedisArray, _rehash);
17 |
18 | PHP_METHOD(RedisArray, select);
19 | PHP_METHOD(RedisArray, info);
20 | PHP_METHOD(RedisArray, ping);
21 | PHP_METHOD(RedisArray, flushdb);
22 | PHP_METHOD(RedisArray, flushall);
23 | PHP_METHOD(RedisArray, mget);
24 | PHP_METHOD(RedisArray, mset);
25 | PHP_METHOD(RedisArray, del);
26 | PHP_METHOD(RedisArray, keys);
27 | PHP_METHOD(RedisArray, getOption);
28 | PHP_METHOD(RedisArray, setOption);
29 | PHP_METHOD(RedisArray, save);
30 | PHP_METHOD(RedisArray, bgsave);
31 |
32 | PHP_METHOD(RedisArray, multi);
33 | PHP_METHOD(RedisArray, exec);
34 | PHP_METHOD(RedisArray, discard);
35 | PHP_METHOD(RedisArray, unwatch);
36 |
37 |
38 | typedef struct RedisArray_ {
39 |
40 | int count;
41 | char **hosts; /* array of host:port strings */
42 | zval *redis; /* array of Redis instances */
43 | zval *z_multi_exec; /* Redis instance to be used in multi-exec */
44 | zend_bool index; /* use per-node index */
45 | zend_bool auto_rehash; /* migrate keys on read operations */
46 | zend_bool pconnect; /* should we use pconnect */
47 | zval z_fun; /* key extractor, callable */
48 | zval z_dist; /* key distributor, callable */
49 | zval z_pure_cmds; /* hash table */
50 | double connect_timeout; /* socket connect timeout */
51 |
52 | struct RedisArray_ *prev;
53 | } RedisArray;
54 |
55 | uint32_t rcrc32(const char *s, size_t sz);
56 |
57 |
58 | #endif
59 |
--------------------------------------------------------------------------------
/redis_array_impl.h:
--------------------------------------------------------------------------------
1 | #ifndef REDIS_ARRAY_IMPL_H
2 | #define REDIS_ARRAY_IMPL_H
3 |
4 | #ifdef PHP_WIN32
5 | #include
6 | #else
7 | #include
8 | #endif
9 |
10 | #include "common.h"
11 | #include "redis_array.h"
12 |
13 | RedisArray *ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b_lazy_connect);
14 | RedisArray *ra_load_array(const char *name);
15 | RedisArray *ra_make_array(HashTable *hosts, zval *z_fun, zval *z_dist, HashTable *hosts_prev, zend_bool b_index, zend_bool b_pconnect, long retry_interval, zend_bool b_lazy_connect, double connect_timeout);
16 | zval *ra_find_node_by_name(RedisArray *ra, const char *host, int host_len);
17 | zval *ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos);
18 | void ra_init_function_table(RedisArray *ra);
19 |
20 | void ra_move_key(const char *key, int key_len, zval *z_from, zval *z_to);
21 | char * ra_find_key(RedisArray *ra, zval *z_args, const char *cmd, int *key_len);
22 | void ra_index_multi(zval *z_redis, long multi_value);
23 |
24 | void ra_index_key(const char *key, int key_len, zval *z_redis);
25 | void ra_index_keys(zval *z_pairs, zval *z_redis);
26 | void ra_index_del(zval *z_keys, zval *z_redis);
27 | void ra_index_exec(zval *z_redis, zval *return_value, int keep_all);
28 | void ra_index_discard(zval *z_redis, zval *return_value);
29 | void ra_index_unwatch(zval *z_redis, zval *return_value);
30 | zend_bool ra_is_write_cmd(RedisArray *ra, const char *cmd, int cmd_len);
31 |
32 | void ra_rehash(RedisArray *ra, zend_fcall_info *z_cb, zend_fcall_info_cache *z_cb_cache);
33 |
34 | #endif
35 |
--------------------------------------------------------------------------------
/redis_cluster.h:
--------------------------------------------------------------------------------
1 | #ifndef REDIS_CLUSTER_H
2 | #define REDIS_CLUSTER_H
3 |
4 | #include "cluster_library.h"
5 | #include
6 | #include
7 |
8 | /* Redis cluster hash slots and N-1 which we'll use to find it */
9 | #define REDIS_CLUSTER_SLOTS 16384
10 | #define REDIS_CLUSTER_MOD (REDIS_CLUSTER_SLOTS-1)
11 |
12 | static inline redisCluster *php_redis_fetch_object(zend_object *obj) {
13 | return (redisCluster *)((char *)(obj) - XtOffsetOf(redisCluster, std));
14 | }
15 | #define Z_REDIS_OBJ_P(zv) php_redis_fetch_object(Z_OBJ_P(zv))
16 |
17 | #define REDIS_METHOD_FETCH_OBJECT \
18 | i_obj = Z_REDIS_OBJ_P(object); \
19 | m_obj = i_obj->obj; \
20 | if (!m_obj) { \
21 | php_error_docref(NULL, E_WARNING, "Memcached constructor was not called"); \
22 | return; \
23 | }
24 |
25 | /* Command building/processing is identical for every command */
26 | #define CLUSTER_BUILD_CMD(name, c, cmd, cmd_len, slot) \
27 | redis_##name##_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, &cmd, \
28 | &cmd_len, &slot)
29 |
30 | /* Append information required to handle MULTI commands to the tail of our MULTI
31 | * linked list. */
32 | #define CLUSTER_ENQUEUE_RESPONSE(c, slot, cb, ctx) \
33 | clusterFoldItem *_item; \
34 | _item = emalloc(sizeof(clusterFoldItem)); \
35 | _item->callback = cb; \
36 | _item->slot = slot; \
37 | _item->ctx = ctx; \
38 | _item->next = NULL; \
39 | if(c->multi_head == NULL) { \
40 | c->multi_head = _item; \
41 | c->multi_curr = _item; \
42 | } else { \
43 | c->multi_curr->next = _item; \
44 | c->multi_curr = _item; \
45 | } \
46 |
47 | /* Simple macro to free our enqueued callbacks after we EXEC */
48 | #define CLUSTER_FREE_QUEUE(c) \
49 | _item = c->multi_head; \
50 | while(_item) { \
51 | _tmp = _item->next; \
52 | efree(_item); \
53 | _item = _tmp; \
54 | } \
55 | c->multi_head = c->multi_curr = NULL; \
56 |
57 | /* Reset anything flagged as MULTI */
58 | #define CLUSTER_RESET_MULTI(c) \
59 | ZEND_HASH_FOREACH_PTR(c->nodes, _node) { \
60 | _node->sock->watching = 0; \
61 | _node->sock->mode = ATOMIC; \
62 | } ZEND_HASH_FOREACH_END(); \
63 | c->flags->watching = 0; \
64 | c->flags->mode = ATOMIC; \
65 |
66 | /* Simple 1-1 command -> response macro */
67 | #define CLUSTER_PROCESS_CMD(cmdname, resp_func, readcmd) \
68 | char *cmd; int cmd_len; short slot; void *ctx = NULL; \
69 | redisCluster *c = Z_REDIS_OBJ_P(getThis()); \
70 | c->readonly = CLUSTER_IS_ATOMIC(c) && readcmd; \
71 | if (redis_##cmdname##_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, &cmd, \
72 | &cmd_len, &slot, &ctx)==FAILURE) { \
73 | return; \
74 | } \
75 | if (cluster_send_command(c, slot, cmd, cmd_len) < 0 || c->err != NULL) {\
76 | efree(cmd); \
77 | RETURN_FALSE; \
78 | } \
79 | efree(cmd); \
80 | if (c->flags->mode == MULTI) { \
81 | CLUSTER_ENQUEUE_RESPONSE(c, slot, resp_func, ctx); \
82 | RETURN_ZVAL(getThis(), 1, 0); \
83 | } \
84 | resp_func(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, ctx);
85 |
86 | /* More generic processing, where only the keyword differs */
87 | #define CLUSTER_PROCESS_KW_CMD(kw, cmdfunc, resp_func, readcmd) \
88 | char *cmd; int cmd_len; short slot; void *ctx = NULL; \
89 | redisCluster *c = Z_REDIS_OBJ_P(getThis()); \
90 | c->readonly = CLUSTER_IS_ATOMIC(c) && readcmd; \
91 | if (cmdfunc(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, kw, &cmd, &cmd_len,\
92 | &slot, &ctx) == FAILURE) { \
93 | return; \
94 | } \
95 | if (cluster_send_command(c, slot, cmd, cmd_len) < 0 || c->err!=NULL) { \
96 | efree(cmd); \
97 | RETURN_FALSE; \
98 | } \
99 | efree(cmd); \
100 | if (c->flags->mode == MULTI) { \
101 | CLUSTER_ENQUEUE_RESPONSE(c, slot, resp_func, ctx); \
102 | RETURN_ZVAL(getThis(), 1, 0); \
103 | } \
104 | resp_func(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, ctx);
105 |
106 | /* For the creation of RedisCluster specific exceptions */
107 | PHP_REDIS_API zend_class_entry *rediscluster_get_exception_base(int root);
108 |
109 | /* Create cluster context */
110 | zend_object * create_cluster_context(zend_class_entry *class_type);
111 |
112 | /* Free cluster context struct */
113 | void free_cluster_context(void *object);
114 |
115 | /* Inittialize our class with PHP */
116 | void init_rediscluster();
117 |
118 | /* RedisCluster method implementation */
119 | PHP_METHOD(RedisCluster, __construct);
120 | PHP_METHOD(RedisCluster, close);
121 | PHP_METHOD(RedisCluster, get);
122 | PHP_METHOD(RedisCluster, set);
123 | PHP_METHOD(RedisCluster, mget);
124 | PHP_METHOD(RedisCluster, mset);
125 | PHP_METHOD(RedisCluster, msetnx);
126 | PHP_METHOD(RedisCluster, mset);
127 | PHP_METHOD(RedisCluster, del);
128 | PHP_METHOD(RedisCluster, dump);
129 | PHP_METHOD(RedisCluster, setex);
130 | PHP_METHOD(RedisCluster, psetex);
131 | PHP_METHOD(RedisCluster, setnx);
132 | PHP_METHOD(RedisCluster, getset);
133 | PHP_METHOD(RedisCluster, exists);
134 | PHP_METHOD(RedisCluster, keys);
135 | PHP_METHOD(RedisCluster, type);
136 | PHP_METHOD(RedisCluster, persist);
137 | PHP_METHOD(RedisCluster, lpop);
138 | PHP_METHOD(RedisCluster, rpop);
139 | PHP_METHOD(RedisCluster, spop);
140 | PHP_METHOD(RedisCluster, rpush);
141 | PHP_METHOD(RedisCluster, lpush);
142 | PHP_METHOD(RedisCluster, blpop);
143 | PHP_METHOD(RedisCluster, brpop);
144 | PHP_METHOD(RedisCluster, rpushx);
145 | PHP_METHOD(RedisCluster, lpushx);
146 | PHP_METHOD(RedisCluster, linsert);
147 | PHP_METHOD(RedisCluster, lindex);
148 | PHP_METHOD(RedisCluster, lrem);
149 | PHP_METHOD(RedisCluster, brpoplpush);
150 | PHP_METHOD(RedisCluster, rpoplpush);
151 | PHP_METHOD(RedisCluster, llen);
152 | PHP_METHOD(RedisCluster, scard);
153 | PHP_METHOD(RedisCluster, smembers);
154 | PHP_METHOD(RedisCluster, sismember);
155 | PHP_METHOD(RedisCluster, sadd);
156 | PHP_METHOD(RedisCluster, srem);
157 | PHP_METHOD(RedisCluster, sunion);
158 | PHP_METHOD(RedisCluster, sunionstore);
159 | PHP_METHOD(RedisCluster, sinter);
160 | PHP_METHOD(RedisCluster, sinterstore);
161 | PHP_METHOD(RedisCluster, sdiff);
162 | PHP_METHOD(RedisCluster, sdiffstore);
163 | PHP_METHOD(RedisCluster, strlen);
164 | PHP_METHOD(RedisCluster, ttl);
165 | PHP_METHOD(RedisCluster, pttl);
166 | PHP_METHOD(RedisCluster, zcard);
167 | PHP_METHOD(RedisCluster, zscore);
168 | PHP_METHOD(RedisCluster, zcount);
169 | PHP_METHOD(RedisCluster, zrem);
170 | PHP_METHOD(RedisCluster, zremrangebyscore);
171 | PHP_METHOD(RedisCluster, zrank);
172 | PHP_METHOD(RedisCluster, zrevrank);
173 | PHP_METHOD(RedisCluster, zadd);
174 | PHP_METHOD(RedisCluster, zincrby);
175 | PHP_METHOD(RedisCluster, hlen);
176 | PHP_METHOD(RedisCluster, hget);
177 | PHP_METHOD(RedisCluster, hkeys);
178 | PHP_METHOD(RedisCluster, hvals);
179 | PHP_METHOD(RedisCluster, hmget);
180 | PHP_METHOD(RedisCluster, hmset);
181 | PHP_METHOD(RedisCluster, hdel);
182 | PHP_METHOD(RedisCluster, hgetall);
183 | PHP_METHOD(RedisCluster, hexists);
184 | PHP_METHOD(RedisCluster, hincrby);
185 | PHP_METHOD(RedisCluster, hincrbyfloat);
186 | PHP_METHOD(RedisCluster, hset);
187 | PHP_METHOD(RedisCluster, hsetnx);
188 | PHP_METHOD(RedisCluster, incr);
189 | PHP_METHOD(RedisCluster, decr);
190 | PHP_METHOD(RedisCluster, incrby);
191 | PHP_METHOD(RedisCluster, decrby);
192 | PHP_METHOD(RedisCluster, incrbyfloat);
193 | PHP_METHOD(RedisCluster, expire);
194 | PHP_METHOD(RedisCluster, expireat);
195 | PHP_METHOD(RedisCluster, pexpire);
196 | PHP_METHOD(RedisCluster, pexpireat);
197 | PHP_METHOD(RedisCluster, append);
198 | PHP_METHOD(RedisCluster, getbit);
199 | PHP_METHOD(RedisCluster, setbit);
200 | PHP_METHOD(RedisCluster, bitop);
201 | PHP_METHOD(RedisCluster, bitpos);
202 | PHP_METHOD(RedisCluster, bitcount);
203 | PHP_METHOD(RedisCluster, lget);
204 | PHP_METHOD(RedisCluster, getrange);
205 | PHP_METHOD(RedisCluster, ltrim);
206 | PHP_METHOD(RedisCluster, lrange);
207 | PHP_METHOD(RedisCluster, zremrangebyrank);
208 | PHP_METHOD(RedisCluster, publish);
209 | PHP_METHOD(RedisCluster, lset);
210 | PHP_METHOD(RedisCluster, rename);
211 | PHP_METHOD(RedisCluster, renamenx);
212 | PHP_METHOD(RedisCluster, pfcount);
213 | PHP_METHOD(RedisCluster, pfadd);
214 | PHP_METHOD(RedisCluster, pfmerge);
215 | PHP_METHOD(RedisCluster, restore);
216 | PHP_METHOD(RedisCluster, setrange);
217 | PHP_METHOD(RedisCluster, smove);
218 | PHP_METHOD(RedisCluster, srandmember);
219 | PHP_METHOD(RedisCluster, zrange);
220 | PHP_METHOD(RedisCluster, zrevrange);
221 | PHP_METHOD(RedisCluster, zrangebyscore);
222 | PHP_METHOD(RedisCluster, zrevrangebyscore);
223 | PHP_METHOD(RedisCluster, zrangebylex);
224 | PHP_METHOD(RedisCluster, zrevrangebylex);
225 | PHP_METHOD(RedisCluster, zlexcount);
226 | PHP_METHOD(RedisCluster, zremrangebylex);
227 | PHP_METHOD(RedisCluster, zunionstore);
228 | PHP_METHOD(RedisCluster, zinterstore);
229 | PHP_METHOD(RedisCluster, sort);
230 | PHP_METHOD(RedisCluster, object);
231 | PHP_METHOD(RedisCluster, subscribe);
232 | PHP_METHOD(RedisCluster, psubscribe);
233 | PHP_METHOD(RedisCluster, unsubscribe);
234 | PHP_METHOD(RedisCluster, punsubscribe);
235 | PHP_METHOD(RedisCluster, eval);
236 | PHP_METHOD(RedisCluster, evalsha);
237 | PHP_METHOD(RedisCluster, info);
238 | PHP_METHOD(RedisCluster, cluster);
239 | PHP_METHOD(RedisCluster, client);
240 | PHP_METHOD(RedisCluster, config);
241 | PHP_METHOD(RedisCluster, pubsub);
242 | PHP_METHOD(RedisCluster, script);
243 | PHP_METHOD(RedisCluster, slowlog);
244 |
245 | /* SCAN and friends */
246 | PHP_METHOD(RedisCluster, scan);
247 | PHP_METHOD(RedisCluster, zscan);
248 | PHP_METHOD(RedisCluster, hscan);
249 | PHP_METHOD(RedisCluster, sscan);
250 |
251 | /* Transactions */
252 | PHP_METHOD(RedisCluster, multi);
253 | PHP_METHOD(RedisCluster, exec);
254 | PHP_METHOD(RedisCluster, discard);
255 | PHP_METHOD(RedisCluster, watch);
256 | PHP_METHOD(RedisCluster, unwatch);
257 |
258 | /* Commands we direct to a node */
259 | PHP_METHOD(RedisCluster, save);
260 | PHP_METHOD(RedisCluster, bgsave);
261 | PHP_METHOD(RedisCluster, flushdb);
262 | PHP_METHOD(RedisCluster, flushall);
263 | PHP_METHOD(RedisCluster, dbsize);
264 | PHP_METHOD(RedisCluster, bgrewriteaof);
265 | PHP_METHOD(RedisCluster, lastsave);
266 | PHP_METHOD(RedisCluster, role);
267 | PHP_METHOD(RedisCluster, time);
268 | PHP_METHOD(RedisCluster, randomkey);
269 | PHP_METHOD(RedisCluster, ping);
270 | PHP_METHOD(RedisCluster, echo);
271 | PHP_METHOD(RedisCluster, rawcommand);
272 |
273 | /* Introspection */
274 | PHP_METHOD(RedisCluster, getmode);
275 | PHP_METHOD(RedisCluster, getlasterror);
276 | PHP_METHOD(RedisCluster, clearlasterror);
277 | PHP_METHOD(RedisCluster, getoption);
278 | PHP_METHOD(RedisCluster, setoption);
279 | PHP_METHOD(RedisCluster, _prefix);
280 | PHP_METHOD(RedisCluster, _serialize);
281 | PHP_METHOD(RedisCluster, _unserialize);
282 | PHP_METHOD(RedisCluster, _masters);
283 | PHP_METHOD(RedisCluster, _redir);
284 |
285 | #endif
286 |
--------------------------------------------------------------------------------
/redis_commands.h:
--------------------------------------------------------------------------------
1 | #ifndef REDIS_COMMANDS_H
2 | #define REDIS_COMMANDS_H
3 |
4 | #include "common.h"
5 | #include "library.h"
6 | #include "cluster_library.h"
7 |
8 | /* Pick a random slot, any slot (for stuff like publish/subscribe) */
9 | #define CMD_RAND_SLOT(slot) do { \
10 | if (slot) { \
11 | *slot = rand() % REDIS_CLUSTER_MOD; \
12 | } \
13 | } while (0)
14 |
15 | /* Macro for setting the slot if we've been asked to */
16 | #define CMD_SET_SLOT(slot, key) do { \
17 | if (slot) { \
18 | *slot = cluster_hash_key(ZSTR_VAL(key), ZSTR_LEN(key)); \
19 | } \
20 | } while (0)
21 |
22 | /* Simple container so we can push subscribe context out */
23 | typedef struct subscribeContext {
24 | char *kw;
25 | int argc;
26 | zend_fcall_info cb;
27 | zend_fcall_info_cache cb_cache;
28 | } subscribeContext;
29 |
30 | /* Redis command generics. Many commands share common prototypes meaning that
31 | * we can write one function to handle all of them. For example, there are
32 | * many COMMAND key value commands, or COMMAND key commands. */
33 |
34 | int redis_empty_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
35 | char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
36 |
37 | int redis_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
38 | char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
39 |
40 | int redis_key_long_val_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
41 | char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
42 |
43 | int redis_key_long_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
44 | char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
45 |
46 | int redis_kv_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
47 | char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
48 |
49 | int redis_key_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
50 | char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
51 |
52 | int redis_key_key_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
53 | char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
54 |
55 | int redis_key_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
56 | char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
57 |
58 | int redis_key_long_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
59 | char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
60 |
61 | int redis_key_long_long_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
62 | char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
63 |
64 | int redis_key_str_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
65 | char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
66 |
67 | int redis_key_dbl_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
68 | char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
69 |
70 | int redis_key_varval_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
71 | char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
72 |
73 | /* Construct SCAN and similar commands, as well as check iterator */
74 | int redis_scan_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
75 | REDIS_SCAN_TYPE type, char **cmd, int *cmd_len);
76 |
77 | /* ZRANGE, ZREVRANGE, ZRANGEBYSCORE, and ZREVRANGEBYSCORE callback type */
78 | typedef int (*zrange_cb)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
79 | char *,char**,int*,int*,short*,void**);
80 |
81 | int redis_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
82 | char *kw, char **cmd, int *cmd_len, int *withscores, short *slot,
83 | void **ctx);
84 |
85 | int redis_zrangebyscore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
86 | char *kw, char **cmd, int *cmd_len, int *withscores, short *slot,
87 | void **ctx);
88 |
89 | int redis_zinter_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
90 | char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
91 |
92 | int redis_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
93 | char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
94 |
95 | int redis_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
96 | char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
97 |
98 | int redis_zrangebylex_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
99 | char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
100 |
101 | int redis_gen_zlex_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
102 | char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
103 |
104 | /* Commands which need a unique construction mechanism. This is either because
105 | * they don't share a signature with any other command, or because there is
106 | * specific processing we do (e.g. verifying subarguments) that make them
107 | * unique */
108 |
109 | int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
110 | char **cmd, int *cmd_len, short *slot, void **ctx);
111 |
112 | int redis_brpoplpush_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
113 | char **cmd, int *cmd_len, short *slot, void **ctx);
114 |
115 | int redis_incr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
116 | char **cmd, int *cmd_len, short *slot, void **ctx);
117 |
118 | int redis_decr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
119 | char **cmd, int *cmd_len, short *slot, void **ctx);
120 |
121 | int redis_hincrby_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
122 | char **cmd, int *cmd_len, short *slot, void **ctx);
123 |
124 | int redis_hincrbyfloat_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
125 | char **cmd, int *cmd_len, short *slot, void **ctx);
126 |
127 | int redis_hmget_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
128 | char **cmd, int *cmd_len, short *slot, void **ctx);
129 |
130 | int redis_hmset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
131 | char **cmd, int *cmd_len, short *slot, void **ctx);
132 |
133 | int redis_bitop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
134 | char **cmd, int *cmd_len, short *slot, void **ctx);
135 |
136 | int redis_bitcount_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
137 | char **cmd, int *cmd_len, short *slot, void **ctx);
138 |
139 | int redis_bitpos_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
140 | char **cmd, int *cmd_len, short *slot, void **ctx);
141 |
142 | int redis_pfcount_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
143 | char **cmd, int *cmd_len, short *slot, void **ctx);
144 |
145 | int redis_pfadd_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
146 | char **cmd, int *cmd_len, short *slot, void **ctx);
147 |
148 | int redis_pfmerge_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
149 | char **cmd, int *cmd_len, short *slot, void **ctx);
150 |
151 | int redis_auth_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
152 | char **cmd, int *cmd_len, short *slot, void **ctx);
153 |
154 | int redis_setbit_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
155 | char **cmd, int *cmd_len, short *slot, void **ctx);
156 |
157 | int redis_linsert_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
158 | char **cmd, int *cmd_len, short *slot, void **ctx);
159 |
160 | int redis_lrem_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
161 | char **cmd, int *cmd_len, short *slot, void **ctx);
162 |
163 | int redis_smove_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
164 | char **cmd, int *cmd_len, short *slot, void **ctx);
165 |
166 | int redis_hset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
167 | char **cmd, int *cmd_len, short *slot, void **ctx);
168 |
169 | int redis_hsetnx_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
170 | char **cmd, int *cmd_len, short *slot, void **ctx);
171 |
172 | int redis_srandmember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
173 | char **cmd, int *cmd_len, short *slot, void **ctx, short *have_count);
174 |
175 | int redis_zincrby_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
176 | char **cmd, int *cmd_len, short *slot, void **ctx);
177 |
178 | int redis_sort_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
179 | int *using_store, char **cmd, int *cmd_len, short *slot, void **ctx);
180 |
181 | int redis_hdel_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
182 | char **cmd, int *cmd_len, short *slot, void **ctx);
183 |
184 | int redis_zadd_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
185 | char **cmd, int *cmd_len, short *slot, void **ctx);
186 |
187 | int redis_object_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
188 | REDIS_REPLY_TYPE *rtype, char **cmd, int *cmd_len, short *slot,
189 | void **ctx);
190 |
191 | int redis_del_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
192 | char **cmd, int *cmd_len, short *slot, void **ctx);
193 |
194 | int redis_watch_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
195 | char **cmd, int *cmd_len, short *slot, void **ctx);
196 |
197 | int redis_blpop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
198 | char **cmd, int *cmd_len, short *slot, void **ctx);
199 |
200 | int redis_brpop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
201 | char **cmd, int *cmd_len, short *slot, void **ctx);
202 |
203 | int redis_sinter_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
204 | char **cmd, int *cmd_len, short *slot, void **ctx);
205 |
206 | int redis_sinterstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
207 | char **cmd, int *cmd_len, short *slot, void **ctx);
208 |
209 | int redis_sunion_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
210 | char **cmd, int *cmd_len, short *slot, void **ctx);
211 |
212 | int redis_sunionstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
213 | char **cmd, int *cmd_len, short *slot, void **ctx);
214 |
215 | int redis_sdiff_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
216 | char **cmd, int *cmd_len, short *slot, void **ctx);
217 |
218 | int redis_sdiffstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
219 | char **cmd, int *cmd_len, short *slot, void **ctx);
220 |
221 | int redis_rawcommand_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
222 | char **cmd, int *cmd_len, short *slot, void **ctx);
223 |
224 | int redis_fmt_scan_cmd(char **cmd, REDIS_SCAN_TYPE type, char *key, int key_len,
225 | long it, char *pat, int pat_len, long count);
226 |
227 | /* Commands that don't communicate with Redis at all (such as getOption,
228 | * setOption, _prefix, _serialize, etc). These can be handled in one place
229 | * with the method of grabbing our RedisSock* object in different ways
230 | * depending if this is a Redis object or a RedisCluster object. */
231 |
232 | void redis_getoption_handler(INTERNAL_FUNCTION_PARAMETERS,
233 | RedisSock *redis_sock, redisCluster *c);
234 | void redis_setoption_handler(INTERNAL_FUNCTION_PARAMETERS,
235 | RedisSock *redis_sock, redisCluster *c);
236 | void redis_prefix_handler(INTERNAL_FUNCTION_PARAMETERS,
237 | RedisSock *redis_sock);
238 | void redis_serialize_handler(INTERNAL_FUNCTION_PARAMETERS,
239 | RedisSock *redis_sock);
240 | void redis_unserialize_handler(INTERNAL_FUNCTION_PARAMETERS,
241 | RedisSock *redis_sock, zend_class_entry *ex);
242 |
243 | #endif
244 |
245 | /* vim: set tabstop=4 softtabstop=4 noexpandtab shiftwidth=4: */
246 |
--------------------------------------------------------------------------------
/redis_session.c:
--------------------------------------------------------------------------------
1 | /* -*- Mode: C; tab-width: 4 -*- */
2 | /*
3 | +----------------------------------------------------------------------+
4 | | PHP Version 5 |
5 | +----------------------------------------------------------------------+
6 | | Copyright (c) 1997-2009 The PHP Group |
7 | +----------------------------------------------------------------------+
8 | | This source file is subject to version 3.01 of the PHP license, |
9 | | that is bundled with this package in the file LICENSE, and is |
10 | | available through the world-wide-web at the following url: |
11 | | http://www.php.net/license/3_01.txt |
12 | | If you did not receive a copy of the PHP license and are unable to |
13 | | obtain it through the world-wide-web, please send a note to |
14 | | license@php.net so we can mail you a copy immediately. |
15 | +----------------------------------------------------------------------+
16 | | Original author: Alfonso Jimenez |
17 | | Maintainer: Nicolas Favre-Felix |
18 | | Maintainer: Nasreddine Bouafif |
19 | | Maintainer: Michael Grunder |
20 | +----------------------------------------------------------------------+
21 | */
22 |
23 | #include "common.h"
24 |
25 | #ifdef HAVE_CONFIG_H
26 | #include "config.h"
27 | #endif
28 |
29 | #ifdef PHP_SESSION
30 | #include "common.h"
31 | #include "ext/standard/info.h"
32 | #include "php_redis.h"
33 | #include "redis_session.h"
34 | #include
35 |
36 | #include "library.h"
37 |
38 | #include "php.h"
39 | #include "php_ini.h"
40 | #include "php_variables.h"
41 | #include "SAPI.h"
42 | #include "ext/standard/url.h"
43 |
44 | ps_module ps_mod_redis = {
45 | PS_MOD(redis)
46 | };
47 |
48 | typedef struct redis_pool_member_ {
49 |
50 | RedisSock *redis_sock;
51 | int weight;
52 | int database;
53 |
54 | char *prefix;
55 | size_t prefix_len;
56 |
57 | char *auth;
58 | size_t auth_len;
59 |
60 | struct redis_pool_member_ *next;
61 |
62 | } redis_pool_member;
63 |
64 | typedef struct {
65 |
66 | int totalWeight;
67 | int count;
68 |
69 | redis_pool_member *head;
70 |
71 | } redis_pool;
72 |
73 | PHP_REDIS_API redis_pool*
74 | redis_pool_new() {
75 | return ecalloc(1, sizeof(redis_pool));
76 | }
77 |
78 | PHP_REDIS_API void
79 | redis_pool_add(redis_pool *pool, RedisSock *redis_sock, int weight,
80 | int database, char *prefix, char *auth) {
81 |
82 | redis_pool_member *rpm = ecalloc(1, sizeof(redis_pool_member));
83 | rpm->redis_sock = redis_sock;
84 | rpm->weight = weight;
85 | rpm->database = database;
86 |
87 | rpm->prefix = prefix;
88 | rpm->prefix_len = (prefix?strlen(prefix):0);
89 |
90 | rpm->auth = auth;
91 | rpm->auth_len = (auth?strlen(auth):0);
92 |
93 | rpm->next = pool->head;
94 | pool->head = rpm;
95 |
96 | pool->totalWeight += weight;
97 | }
98 |
99 | PHP_REDIS_API void
100 | redis_pool_free(redis_pool *pool) {
101 |
102 | redis_pool_member *rpm, *next;
103 | rpm = pool->head;
104 | while(rpm) {
105 | next = rpm->next;
106 | redis_sock_disconnect(rpm->redis_sock);
107 | efree(rpm->redis_sock);
108 | if(rpm->prefix) efree(rpm->prefix);
109 | if(rpm->auth) efree(rpm->auth);
110 | efree(rpm);
111 | rpm = next;
112 | }
113 | efree(pool);
114 | }
115 |
116 | void
117 | redis_pool_member_auth(redis_pool_member *rpm) {
118 | RedisSock *redis_sock = rpm->redis_sock;
119 | char *response, *cmd;
120 | size_t response_len;
121 | int cmd_len;
122 |
123 | if(!rpm->auth || !rpm->auth_len) { /* no password given. */
124 | return;
125 | }
126 | cmd_len = redis_cmd_format_static(&cmd, "AUTH", "s", rpm->auth,
127 | rpm->auth_len);
128 |
129 | if(redis_sock_write(redis_sock, cmd, cmd_len) >= 0) {
130 | if ((response = redis_sock_read(redis_sock, &response_len))) {
131 | efree(response);
132 | }
133 | }
134 | efree(cmd);
135 | }
136 |
137 | static void
138 | redis_pool_member_select(redis_pool_member *rpm) {
139 | RedisSock *redis_sock = rpm->redis_sock;
140 | char *response, *cmd;
141 | size_t response_len;
142 | int cmd_len;
143 |
144 | cmd_len = redis_cmd_format_static(&cmd, "SELECT", "d", rpm->database);
145 |
146 | if(redis_sock_write(redis_sock, cmd, cmd_len) >= 0) {
147 | if ((response = redis_sock_read(redis_sock, &response_len))) {
148 | efree(response);
149 | }
150 | }
151 | efree(cmd);
152 | }
153 |
154 | PHP_REDIS_API redis_pool_member *
155 | redis_pool_get_sock(redis_pool *pool, const char *key) {
156 |
157 | unsigned int pos, i;
158 | redis_pool_member *rpm = pool->head;
159 | memcpy(&pos, key, sizeof(pos));
160 | pos %= pool->totalWeight;
161 |
162 |
163 | for(i = 0; i < pool->totalWeight;) {
164 | if(pos >= i && pos < i + rpm->weight) {
165 | int needs_auth = 0;
166 | if(rpm->auth && rpm->auth_len && rpm->redis_sock->status != REDIS_SOCK_STATUS_CONNECTED) {
167 | needs_auth = 1;
168 | }
169 | redis_sock_server_open(rpm->redis_sock, 0);
170 | if(needs_auth) {
171 | redis_pool_member_auth(rpm);
172 | }
173 | if(rpm->database >= 0) { /* default is -1 which leaves the choice to redis. */
174 | redis_pool_member_select(rpm);
175 | }
176 |
177 | return rpm;
178 | }
179 | i += rpm->weight;
180 | rpm = rpm->next;
181 | }
182 |
183 | return NULL;
184 | }
185 |
186 | /* {{{ PS_OPEN_FUNC
187 | */
188 | PS_OPEN_FUNC(redis)
189 | {
190 |
191 | php_url *url;
192 | zval params, *param;
193 | int i, j, path_len;
194 | RedisSock *redis_sock;
195 | int weight;
196 | double timeout;
197 | int persistent;
198 | int database;
199 | char *prefix, *auth, *persistent_id;
200 | long retry_interval;
201 |
202 | redis_pool *pool = redis_pool_new();
203 |
204 | for (i=0,j=0,path_len=strlen(save_path); iquery != NULL) {
248 | array_init(¶ms);
249 |
250 | sapi_module.treat_data(PARSE_STRING, estrdup(url->query), ¶ms);
251 |
252 | if ((param = zend_hash_str_find(Z_ARRVAL(params), "weight", sizeof("weight") - 1)) != NULL) {
253 | convert_to_long(param);
254 | weight = Z_LVAL_P(param);
255 | }
256 | if ((param = zend_hash_str_find(Z_ARRVAL(params), "timeout", sizeof("timeout") - 1)) != NULL) {
257 | timeout = atof(Z_STRVAL_P(param));
258 | }
259 | if ((param = zend_hash_str_find(Z_ARRVAL(params), "persistent", sizeof("persistent") - 1)) != NULL) {
260 | persistent = (atol(Z_STRVAL_P(param)) == 1 ? 1 : 0);
261 | }
262 | if ((param = zend_hash_str_find(Z_ARRVAL(params), "persistent_id", sizeof("persistent_id") - 1)) != NULL) {
263 | persistent_id = estrndup(Z_STRVAL_P(param), Z_STRLEN_P(param));
264 | }
265 | if ((param = zend_hash_str_find(Z_ARRVAL(params), "prefix", sizeof("prefix") - 1)) != NULL) {
266 | prefix = estrndup(Z_STRVAL_P(param), Z_STRLEN_P(param));
267 | }
268 | if ((param = zend_hash_str_find(Z_ARRVAL(params), "auth", sizeof("auth") - 1)) != NULL) {
269 | auth = estrndup(Z_STRVAL_P(param), Z_STRLEN_P(param));
270 | }
271 | if ((param = zend_hash_str_find(Z_ARRVAL(params), "database", sizeof("database") - 1)) != NULL) {
272 | convert_to_long(param);
273 | database = Z_LVAL_P(param);
274 | }
275 | if ((param = zend_hash_str_find(Z_ARRVAL(params), "retry_interval", sizeof("retry_interval") - 1)) != NULL) {
276 | convert_to_long(param);
277 | retry_interval = Z_LVAL_P(param);
278 | }
279 |
280 | zval_ptr_dtor(¶ms);
281 | }
282 |
283 | if ((url->path == NULL && url->host == NULL) || weight <= 0 || timeout <= 0) {
284 | php_url_free(url);
285 | redis_pool_free(pool);
286 | PS_SET_MOD_DATA(NULL);
287 | return FAILURE;
288 | }
289 |
290 |
291 | if(url->host) {
292 | redis_sock = redis_sock_create(url->host, strlen(url->host), url->port, timeout, persistent, persistent_id, retry_interval, 0);
293 | } else { /* unix */
294 | redis_sock = redis_sock_create(url->path, strlen(url->path), 0, timeout, persistent, persistent_id, retry_interval, 0);
295 | }
296 | redis_pool_add(pool, redis_sock, weight, database, prefix, auth);
297 |
298 | php_url_free(url);
299 | }
300 | }
301 |
302 | if (pool->head) {
303 | PS_SET_MOD_DATA(pool);
304 | return SUCCESS;
305 | }
306 |
307 | return FAILURE;
308 | }
309 | /* }}} */
310 |
311 | /* {{{ PS_CLOSE_FUNC
312 | */
313 | PS_CLOSE_FUNC(redis)
314 | {
315 | redis_pool *pool = PS_GET_MOD_DATA();
316 |
317 | if(pool){
318 | redis_pool_free(pool);
319 | PS_SET_MOD_DATA(NULL);
320 | }
321 | return SUCCESS;
322 | }
323 | /* }}} */
324 |
325 | static char *
326 | redis_session_key(redis_pool_member *rpm, const char *key, int key_len, int *session_len) {
327 |
328 | char *session;
329 | char default_prefix[] = "PHPREDIS_SESSION:";
330 | char *prefix = default_prefix;
331 | size_t prefix_len = sizeof(default_prefix)-1;
332 |
333 | if(rpm->prefix) {
334 | prefix = rpm->prefix;
335 | prefix_len = rpm->prefix_len;
336 | }
337 |
338 | /* build session key */
339 | *session_len = key_len + prefix_len;
340 | session = emalloc(*session_len);
341 | memcpy(session, prefix, prefix_len);
342 | memcpy(session + prefix_len, key, key_len);
343 |
344 | return session;
345 | }
346 |
347 |
348 | /* {{{ PS_READ_FUNC
349 | */
350 | PS_READ_FUNC(redis)
351 | {
352 | char *session, *cmd;
353 | int session_len, cmd_len;
354 | char *tmp_val = NULL;
355 | size_t temp_len = 0;
356 |
357 | redis_pool *pool = PS_GET_MOD_DATA();
358 | redis_pool_member *rpm = redis_pool_get_sock(pool, key->val);
359 | RedisSock *redis_sock = rpm?rpm->redis_sock:NULL;
360 | if(!rpm || !redis_sock){
361 | return FAILURE;
362 | }
363 |
364 | /* send GET command */
365 | session = redis_session_key(rpm, key->val, key->len, &session_len);
366 | cmd_len = redis_cmd_format_static(&cmd, "GET", "s", session, session_len);
367 |
368 | efree(session);
369 | if(redis_sock_write(redis_sock, cmd, cmd_len) < 0) {
370 | efree(cmd);
371 | return FAILURE;
372 | }
373 |
374 | efree(cmd);
375 |
376 | /* read response */
377 | if ((tmp_val = redis_sock_read(redis_sock, &temp_len)) == NULL) {
378 | return FAILURE;
379 | }
380 |
381 | *val = zend_string_init(tmp_val, temp_len, 1);
382 | return SUCCESS;
383 | }
384 | /* }}} */
385 |
386 | /* {{{ PS_WRITE_FUNC
387 | */
388 | PS_WRITE_FUNC(redis)
389 | {
390 | char *cmd, *response, *session;
391 | size_t cmd_len, response_len;
392 | int session_len;
393 |
394 | redis_pool *pool = PS_GET_MOD_DATA();
395 | redis_pool_member *rpm = redis_pool_get_sock(pool, key->val);
396 | RedisSock *redis_sock = rpm?rpm->redis_sock:NULL;
397 | if(!rpm || !redis_sock){
398 | return FAILURE;
399 | }
400 |
401 | /* send SET command */
402 | session = redis_session_key(rpm, key->val, key->len, &session_len);
403 | cmd_len = redis_cmd_format_static(&cmd, "SETEX", "sds",
404 | session, session_len,
405 | INI_INT("session.gc_maxlifetime"),
406 | val->val, val->len);
407 | efree(session);
408 | if(redis_sock_write(redis_sock, cmd, cmd_len) < 0) {
409 | efree(cmd);
410 | return FAILURE;
411 | }
412 |
413 | efree(cmd);
414 |
415 | /* read response */
416 | if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) {
417 | return FAILURE;
418 | }
419 |
420 | if(response_len == 3 && strncmp(response, "+OK", 3) == 0) {
421 | efree(response);
422 | return SUCCESS;
423 | } else {
424 | efree(response);
425 | return FAILURE;
426 | }
427 | }
428 | /* }}} */
429 |
430 | /* {{{ PS_DESTROY_FUNC
431 | */
432 | PS_DESTROY_FUNC(redis)
433 | {
434 | char *cmd, *response, *session;
435 | int cmd_len, session_len;
436 | size_t response_len;
437 |
438 | redis_pool *pool = PS_GET_MOD_DATA();
439 | redis_pool_member *rpm = redis_pool_get_sock(pool, key->val);
440 | RedisSock *redis_sock = rpm?rpm->redis_sock:NULL;
441 | if(!rpm || !redis_sock){
442 | return FAILURE;
443 | }
444 |
445 | /* send DEL command */
446 | session = redis_session_key(rpm, key->val, key->len, &session_len);
447 | cmd_len = redis_cmd_format_static(&cmd, "DEL", "s", session, session_len);
448 | efree(session);
449 | if(redis_sock_write(redis_sock, cmd, cmd_len) < 0) {
450 | efree(cmd);
451 | return FAILURE;
452 | }
453 | efree(cmd);
454 |
455 | /* read response */
456 | if ((response = redis_sock_read(redis_sock, &response_len)) == NULL) {
457 | return FAILURE;
458 | }
459 |
460 | if(response_len == 2 && response[0] == ':' && (response[1] == '0' || response[1] == '1')) {
461 | efree(response);
462 | return SUCCESS;
463 | } else {
464 | efree(response);
465 | return FAILURE;
466 | }
467 | }
468 | /* }}} */
469 |
470 | /* {{{ PS_GC_FUNC
471 | */
472 | PS_GC_FUNC(redis)
473 | {
474 | return SUCCESS;
475 | }
476 | /* }}} */
477 |
478 | #endif
479 | /* vim: set tabstop=4 expandtab: */
480 |
--------------------------------------------------------------------------------
/redis_session.h:
--------------------------------------------------------------------------------
1 | #ifndef REDIS_SESSION_H
2 | #define REDIS_SESSION_H
3 | #ifdef PHP_SESSION
4 | #include "ext/session/php_session.h"
5 |
6 | PS_OPEN_FUNC(redis);
7 | PS_CLOSE_FUNC(redis);
8 | PS_READ_FUNC(redis);
9 | PS_WRITE_FUNC(redis);
10 | PS_DESTROY_FUNC(redis);
11 | PS_GC_FUNC(redis);
12 |
13 |
14 | #endif
15 | #endif
16 |
17 |
--------------------------------------------------------------------------------
/rpm/php-redis.spec:
--------------------------------------------------------------------------------
1 | %global php_apiver %((echo 0; php -i 2>/dev/null | sed -n 's/^PHP API => //p') | tail -1)
2 | %global php_extdir %(php-config --extension-dir 2>/dev/null || echo "undefined")
3 | %global php_version %(php-config --version 2>/dev/null || echo 0)
4 |
5 | Name: php-redis
6 | Version: 2.2.5
7 | Release: 1%{?dist}
8 | Summary: The phpredis extension provides an API for communicating with the Redis key-value store.
9 |
10 | Group: Development/Languages
11 | License: PHP
12 | URL: https://github.com/nicolasff/phpredis
13 | Source0: https://github.com/nicolasff/phpredis/tarball/master
14 | Source1: redis.ini
15 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
16 |
17 | BuildRequires: php-devel
18 | Requires: php(zend-abi) = %{php_zend_api}
19 | Requires: php(api) = %{php_apiver}
20 |
21 | %description
22 | The phpredis extension provides an API for communicating with the Redis key-value store.
23 |
24 | %prep
25 | %setup -q -n nicolasff-phpredis-43bc590
26 |
27 | %build
28 | %{_bindir}/phpize
29 | %configure
30 | make %{?_smp_mflags}
31 |
32 | %install
33 | rm -rf $RPM_BUILD_ROOT
34 | make install INSTALL_ROOT=$RPM_BUILD_ROOT
35 |
36 | # install configuration
37 | %{__mkdir} -p $RPM_BUILD_ROOT%{_sysconfdir}/php.d
38 | %{__cp} %{SOURCE1} $RPM_BUILD_ROOT%{_sysconfdir}/php.d/redis.ini
39 |
40 | %clean
41 | rm -rf $RPM_BUILD_ROOT
42 |
43 | %files
44 | %defattr(-,root,root,-)
45 | %doc CREDITS
46 | %config(noreplace) %{_sysconfdir}/php.d/redis.ini
47 | %{php_extdir}/redis.so
48 |
49 |
--------------------------------------------------------------------------------
/rpm/redis.ini:
--------------------------------------------------------------------------------
1 | extension=redis.so
2 |
--------------------------------------------------------------------------------
/serialize.list:
--------------------------------------------------------------------------------
1 | This file lists which methods support serialization. Only indented methods have been ported.
2 |
3 | get
4 | set
5 | setex
6 | setnx
7 | getSet
8 | getMultiple
9 | append
10 | substr
11 | strlen
12 | lPush
13 | lPushx
14 | rPush
15 | rPushx
16 | lPop
17 | rPop
18 | blPop
19 | brPop
20 | lRemove
21 | lGet
22 | lGetRange
23 | lSet
24 | lInsert
25 |
26 | sAdd
27 | sRemove
28 | sMove
29 | sContains
30 |
31 | zAdd
32 | zDelete
33 | zScore
34 | zRank
35 | zRevRank
36 | zIncrBy
37 |
38 | mset
39 |
40 | hGet
41 | hSet
42 | hGetAll
43 | hExists
44 | hMset
45 | hMget
46 |
47 | publish
48 | subscribe
49 | unsubscribe
50 |
--------------------------------------------------------------------------------
/tests/RedisArrayTest.php:
--------------------------------------------------------------------------------
1 | \d+)_\w+#", $str, $out)) {
10 | return $out['facebook_id'];
11 | }
12 | return $str;
13 | }
14 |
15 | class Redis_Array_Test extends TestSuite
16 | {
17 | private $strings;
18 | public $ra = NULL;
19 | private $data = NULL;
20 |
21 | public function setUp() {
22 |
23 | // initialize strings.
24 | $n = REDIS_ARRAY_DATA_SIZE;
25 | $this->strings = array();
26 | for($i = 0; $i < $n; $i++) {
27 | $this->strings['key-'.$i] = 'val-'.$i;
28 | }
29 |
30 | global $newRing, $oldRing, $useIndex;
31 | $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex));
32 | }
33 |
34 | public function testMSet() {
35 | // run mset
36 | $this->assertTrue(TRUE === $this->ra->mset($this->strings));
37 |
38 | // check each key individually using the array
39 | foreach($this->strings as $k => $v) {
40 | $this->assertTrue($v === $this->ra->get($k));
41 | }
42 |
43 | // check each key individually using a new connection
44 | foreach($this->strings as $k => $v) {
45 | list($host, $port) = split(':', $this->ra->_target($k));
46 |
47 | $r = new Redis;
48 | $r->pconnect($host, (int)$port);
49 | $this->assertTrue($v === $r->get($k));
50 | }
51 | }
52 |
53 | public function testMGet() {
54 | $this->assertTrue(array_values($this->strings) === $this->ra->mget(array_keys($this->strings)));
55 | }
56 |
57 | private function addData($commonString) {
58 | $this->data = array();
59 | for($i = 0; $i < REDIS_ARRAY_DATA_SIZE; $i++) {
60 | $k = rand().'_'.$commonString.'_'.rand();
61 | $this->data[$k] = rand();
62 | }
63 | $this->ra->mset($this->data);
64 | }
65 |
66 | private function checkCommonLocality() {
67 | // check that they're all on the same node.
68 | $lastNode = NULL;
69 | foreach($this->data as $k => $v) {
70 | $node = $this->ra->_target($k);
71 | if($lastNode) {
72 | $this->assertTrue($node === $lastNode);
73 | }
74 | $this->assertTrue($this->ra->get($k) == $v);
75 | $lastNode = $node;
76 | }
77 | }
78 |
79 | public function testKeyLocality() {
80 |
81 | // basic key locality with default hash
82 | $this->addData('{hashed part of the key}');
83 | $this->checkCommonLocality();
84 |
85 | // with common hashing function
86 | global $newRing, $oldRing, $useIndex;
87 | $this->ra = new RedisArray($newRing, array('previous' => $oldRing,
88 | 'index' => $useIndex,
89 | 'function' => 'custom_hash'));
90 |
91 | // basic key locality with custom hash
92 | $this->addData('fb'.rand());
93 | $this->checkCommonLocality();
94 | }
95 |
96 | public function customDistributor($key)
97 | {
98 | $a = unpack("N*", md5($key, true));
99 | global $newRing;
100 | $pos = abs($a[1]) % count($newRing);
101 |
102 | return $pos;
103 | }
104 |
105 | public function testKeyDistributor()
106 | {
107 | global $newRing, $useIndex;
108 | $this->ra = new RedisArray($newRing, array(
109 | 'index' => $useIndex,
110 | 'function' => 'custom_hash',
111 | 'distributor' => array($this, "customDistributor")));
112 |
113 | // custom key distribution function.
114 | $this->addData('fb'.rand());
115 |
116 | // check that they're all on the expected node.
117 | $lastNode = NULL;
118 | foreach($this->data as $k => $v) {
119 | $node = $this->ra->_target($k);
120 | $pos = $this->customDistributor($k);
121 | $this->assertTrue($node === $newRing[$pos]);
122 | }
123 | }
124 |
125 | }
126 |
127 | class Redis_Rehashing_Test extends TestSuite
128 | {
129 |
130 | public $ra = NULL;
131 | private $useIndex;
132 |
133 | // data
134 | private $strings;
135 | private $sets;
136 | private $lists;
137 | private $hashes;
138 | private $zsets;
139 |
140 | public function setUp() {
141 |
142 | // initialize strings.
143 | $n = REDIS_ARRAY_DATA_SIZE;
144 | $this->strings = array();
145 | for($i = 0; $i < $n; $i++) {
146 | $this->strings['key-'.$i] = 'val-'.$i;
147 | }
148 |
149 | // initialize sets
150 | for($i = 0; $i < $n; $i++) {
151 | // each set has 20 elements
152 | $this->sets['set-'.$i] = range($i, $i+20);
153 | }
154 |
155 | // initialize lists
156 | for($i = 0; $i < $n; $i++) {
157 | // each list has 20 elements
158 | $this->lists['list-'.$i] = range($i, $i+20);
159 | }
160 |
161 | // initialize hashes
162 | for($i = 0; $i < $n; $i++) {
163 | // each hash has 5 keys
164 | $this->hashes['hash-'.$i] = array('A' => $i, 'B' => $i+1, 'C' => $i+2, 'D' => $i+3, 'E' => $i+4);
165 | }
166 |
167 | // initialize sorted sets
168 | for($i = 0; $i < $n; $i++) {
169 | // each sorted sets has 5 elements
170 | $this->zsets['zset-'.$i] = array($i, 'A', $i+1, 'B', $i+2, 'C', $i+3, 'D', $i+4, 'E');
171 | }
172 |
173 | global $newRing, $oldRing, $useIndex;
174 |
175 | // create array
176 | $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex));
177 | }
178 |
179 | public function testFlush() {
180 | // flush all servers first.
181 | global $serverList;
182 | foreach($serverList as $s) {
183 | list($host, $port) = explode(':', $s);
184 |
185 | $r = new Redis();
186 | $r->pconnect($host, (int)$port, 0);
187 | $r->flushdb();
188 | }
189 | }
190 |
191 |
192 | private function distributeKeys() {
193 |
194 | // strings
195 | foreach($this->strings as $k => $v) {
196 | $this->ra->set($k, $v);
197 | }
198 |
199 | // sets
200 | foreach($this->sets as $k => $v) {
201 | call_user_func_array(array($this->ra, 'sadd'), array_merge(array($k), $v));
202 | }
203 |
204 | // lists
205 | foreach($this->lists as $k => $v) {
206 | call_user_func_array(array($this->ra, 'rpush'), array_merge(array($k), $v));
207 | }
208 |
209 | // hashes
210 | foreach($this->hashes as $k => $v) {
211 | $this->ra->hmset($k, $v);
212 | }
213 |
214 | // sorted sets
215 | foreach($this->zsets as $k => $v) {
216 | call_user_func_array(array($this->ra, 'zadd'), array_merge(array($k), $v));
217 | }
218 | }
219 |
220 | public function testDistribution() {
221 | $this->distributeKeys();
222 | }
223 |
224 | public function testSimpleRead() {
225 | $this->readAllvalues();
226 | }
227 |
228 | private function readAllvalues() {
229 |
230 | // strings
231 | foreach($this->strings as $k => $v) {
232 | $this->assertTrue($this->ra->get($k) === $v);
233 | }
234 |
235 | // sets
236 | foreach($this->sets as $k => $v) {
237 | $ret = $this->ra->smembers($k); // get values
238 |
239 | // sort sets
240 | sort($v);
241 | sort($ret);
242 |
243 | $this->assertTrue($ret == $v);
244 | }
245 |
246 | // lists
247 | foreach($this->lists as $k => $v) {
248 | $ret = $this->ra->lrange($k, 0, -1);
249 | $this->assertTrue($ret == $v);
250 | }
251 |
252 | // hashes
253 | foreach($this->hashes as $k => $v) {
254 | $ret = $this->ra->hgetall($k); // get values
255 | $this->assertTrue($ret == $v);
256 | }
257 |
258 | // sorted sets
259 | foreach($this->zsets as $k => $v) {
260 | $ret = $this->ra->zrange($k, 0, -1, TRUE); // get values with scores
261 |
262 | // create assoc array from local dataset
263 | $tmp = array();
264 | for($i = 0; $i < count($v); $i += 2) {
265 | $tmp[$v[$i+1]] = $v[$i];
266 | }
267 |
268 | // compare to RA value
269 | $this->assertTrue($ret == $tmp);
270 | }
271 | }
272 |
273 | // add a new node.
274 | public function testCreateSecondRing() {
275 |
276 | global $newRing, $oldRing, $serverList;
277 | $oldRing = $newRing; // back up the original.
278 | $newRing = $serverList; // add a new node to the main ring.
279 | }
280 |
281 | public function testReadUsingFallbackMechanism() {
282 | $this->readAllvalues(); // some of the reads will fail and will go to another target node.
283 | }
284 |
285 | public function testRehash() {
286 | $this->ra->_rehash(); // this will redistribute the keys
287 | }
288 |
289 | public function testRehashWithCallback() {
290 | $total = 0;
291 | $this->ra->_rehash(function ($host, $count) use (&$total) {
292 | $total += $count;
293 | });
294 | $this->assertTrue($total > 0);
295 | }
296 |
297 | public function testReadRedistributedKeys() {
298 | $this->readAllvalues(); // we shouldn't have any missed reads now.
299 | }
300 | }
301 |
302 | // Test auto-migration of keys
303 | class Redis_Auto_Rehashing_Test extends TestSuite {
304 |
305 | public $ra = NULL;
306 |
307 | // data
308 | private $strings;
309 |
310 | public function setUp() {
311 | // initialize strings.
312 | $n = REDIS_ARRAY_DATA_SIZE;
313 | $this->strings = array();
314 | for($i = 0; $i < $n; $i++) {
315 | $this->strings['key-'.$i] = 'val-'.$i;
316 | }
317 |
318 | global $newRing, $oldRing, $useIndex;
319 |
320 | // create array
321 | $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex, 'autorehash' => TRUE));
322 | }
323 |
324 | public function testDistribute() {
325 | // strings
326 | foreach($this->strings as $k => $v) {
327 | $this->ra->set($k, $v);
328 | }
329 | }
330 |
331 | private function readAllvalues() {
332 | foreach($this->strings as $k => $v) {
333 | $this->assertTrue($this->ra->get($k) === $v);
334 | }
335 | }
336 |
337 |
338 | public function testReadAll() {
339 | $this->readAllvalues();
340 | }
341 |
342 | // add a new node.
343 | public function testCreateSecondRing() {
344 | global $newRing, $oldRing, $serverList;
345 | $oldRing = $newRing; // back up the original.
346 | $newRing = $serverList; // add a new node to the main ring.
347 | }
348 |
349 | // Read and migrate keys on fallback, causing the whole ring to be rehashed.
350 | public function testReadAndMigrateAll() {
351 | $this->readAllvalues();
352 | }
353 |
354 | // Read and migrate keys on fallback, causing the whole ring to be rehashed.
355 | public function testAllKeysHaveBeenMigrated() {
356 | foreach($this->strings as $k => $v) {
357 | // get the target for each key
358 | $target = $this->ra->_target($k);
359 |
360 | // connect to the target host
361 | list($host,$port) = split(':', $target);
362 | $r = new Redis;
363 | $r->pconnect($host, $port);
364 |
365 | $this->assertTrue($v === $r->get($k)); // check that the key has actually been migrated to the new node.
366 | }
367 | }
368 | }
369 |
370 | // Test node-specific multi/exec
371 | class Redis_Multi_Exec_Test extends TestSuite {
372 | public $ra = NULL;
373 |
374 | public function setUp() {
375 | global $newRing, $oldRing, $useIndex;
376 | // create array
377 | $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex));
378 | }
379 |
380 | public function testInit() {
381 | $this->ra->set('{groups}:managers', 2);
382 | $this->ra->set('{groups}:executives', 3);
383 |
384 | $this->ra->set('1_{employee:joe}_name', 'joe');
385 | $this->ra->set('1_{employee:joe}_group', 2);
386 | $this->ra->set('1_{employee:joe}_salary', 2000);
387 | }
388 |
389 | public function testKeyDistribution() {
390 | // check that all of joe's keys are on the same instance
391 | $lastNode = NULL;
392 | foreach(array('name', 'group', 'salary') as $field) {
393 | $node = $this->ra->_target('1_{employee:joe}_'.$field);
394 | if($lastNode) {
395 | $this->assertTrue($node === $lastNode);
396 | }
397 | $lastNode = $node;
398 | }
399 | }
400 |
401 | public function testMultiExec() {
402 |
403 | // Joe gets a promotion
404 | $newGroup = $this->ra->get('{groups}:executives');
405 | $newSalary = 4000;
406 |
407 | // change both in a transaction.
408 | $host = $this->ra->_target('{employee:joe}'); // transactions are per-node, so we need a reference to it.
409 | $tr = $this->ra->multi($host)
410 | ->set('1_{employee:joe}_group', $newGroup)
411 | ->set('1_{employee:joe}_salary', $newSalary)
412 | ->exec();
413 |
414 | // check that the group and salary have been changed
415 | $this->assertTrue($this->ra->get('1_{employee:joe}_group') === $newGroup);
416 | $this->assertTrue($this->ra->get('1_{employee:joe}_salary') == $newSalary);
417 |
418 | }
419 |
420 | public function testMultiExecMSet() {
421 |
422 | global $newGroup, $newSalary;
423 | $newGroup = 1;
424 | $newSalary = 10000;
425 |
426 | // test MSET, making Joe a top-level executive
427 | $out = $this->ra->multi($this->ra->_target('{employee:joe}'))
428 | ->mset(array('1_{employee:joe}_group' => $newGroup, '1_{employee:joe}_salary' => $newSalary))
429 | ->exec();
430 |
431 | $this->assertTrue($out[0] === TRUE);
432 | }
433 |
434 | public function testMultiExecMGet() {
435 |
436 | global $newGroup, $newSalary;
437 |
438 | // test MGET
439 | $out = $this->ra->multi($this->ra->_target('{employee:joe}'))
440 | ->mget(array('1_{employee:joe}_group', '1_{employee:joe}_salary'))
441 | ->exec();
442 |
443 | $this->assertTrue($out[0][0] == $newGroup);
444 | $this->assertTrue($out[0][1] == $newSalary);
445 | }
446 |
447 | public function testMultiExecDel() {
448 |
449 | // test DEL
450 | $out = $this->ra->multi($this->ra->_target('{employee:joe}'))
451 | ->del('1_{employee:joe}_group', '1_{employee:joe}_salary')
452 | ->exec();
453 |
454 | $this->assertTrue($out[0] === 2);
455 | $this->assertTrue($this->ra->exists('1_{employee:joe}_group') === FALSE);
456 | $this->assertTrue($this->ra->exists('1_{employee:joe}_salary') === FALSE);
457 | }
458 |
459 | public function testDiscard() {
460 | /* phpredis issue #87 */
461 | $key = 'test_err';
462 |
463 | $this->assertTrue($this->ra->set($key, 'test'));
464 | $this->assertTrue('test' === $this->ra->get($key));
465 |
466 | $this->ra->watch($key);
467 |
468 | // After watch, same
469 | $this->assertTrue('test' === $this->ra->get($key));
470 |
471 | // change in a multi/exec block.
472 | $ret = $this->ra->multi($this->ra->_target($key))->set($key, 'test1')->exec();
473 | $this->assertTrue($ret === array(true));
474 |
475 | // Get after exec, 'test1':
476 | $this->assertTrue($this->ra->get($key) === 'test1');
477 |
478 | $this->ra->watch($key);
479 |
480 | // After second watch, still test1.
481 | $this->assertTrue($this->ra->get($key) === 'test1');
482 |
483 | $ret = $this->ra->multi($this->ra->_target($key))->set($key, 'test2')->discard();
484 | // Ret after discard: NULL";
485 | $this->assertTrue($ret === NULL);
486 |
487 | // Get after discard, unchanged:
488 | $this->assertTrue($this->ra->get($key) === 'test1');
489 | }
490 |
491 | }
492 |
493 | // Test custom distribution function
494 | class Redis_Distributor_Test extends TestSuite {
495 |
496 | public $ra = NULL;
497 |
498 | public function setUp() {
499 | global $newRing, $oldRing, $useIndex;
500 | // create array
501 | $this->ra = new RedisArray($newRing, array('previous' => $oldRing, 'index' => $useIndex, 'distributor' => array($this, 'distribute')));
502 | }
503 |
504 | public function testInit() {
505 | $this->ra->set('{uk}test', 'joe');
506 | $this->ra->set('{us}test', 'bob');
507 | }
508 |
509 | public function distribute($key) {
510 | $matches = array();
511 | if (preg_match('/{([^}]+)}.*/', $key, $matches) == 1) {
512 | $countries = array('uk' => 0, 'us' => 1);
513 | if (array_key_exists($matches[1], $countries)) {
514 | return $countries[$matches[1]];
515 | }
516 | }
517 | return 2; // default server
518 | }
519 |
520 | public function testDistribution() {
521 | $ukServer = $this->ra->_target('{uk}test');
522 | $usServer = $this->ra->_target('{us}test');
523 | $deServer = $this->ra->_target('{de}test');
524 | $defaultServer = $this->ra->_target('unknown');
525 |
526 | $nodes = $this->ra->_hosts();
527 | $this->assertTrue($ukServer === $nodes[0]);
528 | $this->assertTrue($usServer === $nodes[1]);
529 | $this->assertTrue($deServer === $nodes[2]);
530 | $this->assertTrue($defaultServer === $nodes[2]);
531 | }
532 | }
533 |
534 | function run_tests($className, $str_filter) {
535 | // reset rings
536 | global $newRing, $oldRing, $serverList;
537 | $newRing = array('localhost:6379', 'localhost:6380', 'localhost:6381');
538 | $oldRing = array();
539 | $serverList = array('localhost:6379', 'localhost:6380', 'localhost:6381', 'localhost:6382');
540 |
541 | // run
542 | TestSuite::run($className, $str_filter);
543 | }
544 | ?>
545 |
--------------------------------------------------------------------------------
/tests/RedisClusterTest.php:
--------------------------------------------------------------------------------
1 | markTestSkipped(); }
31 | public function testSortDesc() { return $this->markTestSkipped(); }
32 | public function testWait() { return $this->markTestSkipped(); }
33 | public function testSelect() { return $this->markTestSkipped(); }
34 | public function testReconnectSelect() { return $this->markTestSkipped(); }
35 |
36 | /* Load our seeds on construction */
37 | public function __construct() {
38 | $str_nodemap_file = dirname($_SERVER['PHP_SELF']) . '/nodes/nodemap';
39 |
40 | if (!file_exists($str_nodemap_file)) {
41 | fprintf(STDERR, "Error: Can't find nodemap file for seeds!\n");
42 | exit(1);
43 | }
44 |
45 | /* Store our node map */
46 | $this->_arr_node_map = array_filter(
47 | explode("\n", file_get_contents($str_nodemap_file)
48 | ));
49 | }
50 |
51 | /* Override setUp to get info from a specific node */
52 | public function setUp() {
53 | $this->redis = $this->newInstance();
54 | $info = $this->redis->info(uniqid());
55 | $this->version = (isset($info['redis_version'])?$info['redis_version']:'0.0.0');
56 | }
57 |
58 | /* Override newInstance as we want a RedisCluster object */
59 | protected function newInstance() {
60 | return new RedisCluster(NULL, $this->_arr_node_map);
61 | }
62 |
63 | /* Overrides for RedisTest where the function signature is different. This
64 | * is only true for a few commands, which by definition have to be directed
65 | * at a specific node */
66 |
67 | public function testPing() {
68 | for ($i = 0; $i < 100; $i++) {
69 | $this->assertTrue($this->redis->ping("key:$i"));
70 | }
71 | }
72 |
73 | public function testRandomKey() {
74 | for ($i = 0; $i < 1000; $i++) {
75 | $k = $this->redis->randomKey("key:$i");
76 | $this->assertTrue($this->redis->exists($k));
77 | }
78 | }
79 |
80 | public function testEcho() {
81 | $this->assertEquals($this->redis->echo('k1', 'hello'), 'hello');
82 | $this->assertEquals($this->redis->echo('k2', 'world'), 'world');
83 | $this->assertEquals($this->redis->echo('k3', " 0123 "), " 0123 ");
84 | }
85 |
86 | public function testSortPrefix() {
87 | $this->redis->setOption(Redis::OPT_PREFIX, 'some-prefix:');
88 | $this->redis->del('some-item');
89 | $this->redis->sadd('some-item', 1);
90 | $this->redis->sadd('some-item', 2);
91 | $this->redis->sadd('some-item', 3);
92 |
93 | $this->assertEquals(array('1','2','3'), $this->redis->sort('some-item'));
94 |
95 | // Kill our set/prefix
96 | $this->redis->del('some-item');
97 | $this->redis->setOption(Redis::OPT_PREFIX, '');
98 | }
99 |
100 | public function testDBSize() {
101 | for ($i = 0; $i < 10; $i++) {
102 | $str_key = "key:$i";
103 | $this->assertTrue($this->redis->flushdb($str_key));
104 | $this->redis->set($str_key, "val:$i");
105 | $this->assertEquals(1, $this->redis->dbsize($str_key));
106 | }
107 | }
108 |
109 | public function testInfo() {
110 | $arr_check_keys = Array(
111 | "redis_version", "arch_bits", "uptime_in_seconds", "uptime_in_days",
112 | "connected_clients", "connected_slaves", "used_memory",
113 | "total_connections_received", "total_commands_processed",
114 | "role"
115 | );
116 |
117 | for ($i = 0; $i < 3; $i++) {
118 | $arr_info = $this->redis->info("k:$i");
119 | foreach ($arr_check_keys as $str_check_key) {
120 | $this->assertTrue(isset($arr_info[$str_check_key]));
121 | }
122 | }
123 | }
124 |
125 | public function testClient() {
126 | $str_key = 'key-' . rand(1,100);
127 |
128 | $this->assertTrue($this->redis->client($str_key, 'setname', 'cluster_tests'));
129 |
130 | $arr_clients = $this->redis->client($str_key, 'list');
131 | $this->assertTrue(is_array($arr_clients));
132 |
133 | /* Find us in the list */
134 | $str_addr = NULL;
135 | foreach ($arr_clients as $arr_client) {
136 | if ($arr_client['name'] == 'cluster_tests') {
137 | $str_addr = $arr_client['addr'];
138 | break;
139 | }
140 | }
141 |
142 | /* We should be in there */
143 | $this->assertFalse(empty($str_addr));
144 |
145 | /* Kill our own client! */
146 | $this->assertTrue($this->redis->client($str_key, 'kill', $str_addr));
147 | }
148 |
149 | public function testTime() {
150 | $time_arr = $this->redis->time("k:" . rand(1,100));
151 | $this->assertTrue(is_array($time_arr) && count($time_arr) == 2 &&
152 | strval(intval($time_arr[0])) === strval($time_arr[0]) &&
153 | strval(intval($time_arr[1])) === strval($time_arr[1]));
154 | }
155 |
156 | public function testScan() {
157 | $i_key_count = 0;
158 | $i_scan_count = 0;
159 |
160 | /* Have scan retry for us */
161 | $this->redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
162 |
163 | /* Iterate over our masters, scanning each one */
164 | foreach ($this->redis->_masters() as $arr_master) {
165 | /* Grab the number of keys we have */
166 | $i_key_count += $this->redis->dbsize($arr_master);
167 |
168 | /* Scan the keys here */
169 | $it = NULL;
170 | while ($arr_keys = $this->redis->scan($it, $arr_master)) {
171 | $i_scan_count += count($arr_keys);
172 | }
173 | }
174 |
175 | /* Our total key count should match */
176 | $this->assertEquals($i_scan_count, $i_key_count);
177 | }
178 |
179 | // Run some simple tests against the PUBSUB command. This is problematic, as we
180 | // can't be sure what's going on in the instance, but we can do some things.
181 | public function testPubSub() {
182 | // PUBSUB CHANNELS ...
183 | $result = $this->redis->pubsub("somekey", "channels", "*");
184 | $this->assertTrue(is_array($result));
185 | $result = $this->redis->pubsub("somekey", "channels");
186 | $this->assertTrue(is_array($result));
187 |
188 | // PUBSUB NUMSUB
189 |
190 | $c1 = '{pubsub}-' . rand(1,100);
191 | $c2 = '{pubsub}-' . rand(1,100);
192 |
193 | $result = $this->redis->pubsub("{pubsub}", "numsub", $c1, $c2);
194 |
195 | // Should get an array back, with two elements
196 | $this->assertTrue(is_array($result));
197 | $this->assertEquals(count($result), 4);
198 |
199 | $arr_zipped = Array();
200 | for ($i = 0; $i <= count($result) / 2; $i+=2) {
201 | $arr_zipped[$result[$i]] = $result[$i+1];
202 | }
203 | $result = $arr_zipped;
204 |
205 | // Make sure the elements are correct, and have zero counts
206 | foreach(Array($c1,$c2) as $channel) {
207 | $this->assertTrue(isset($result[$channel]));
208 | $this->assertEquals($result[$channel], 0);
209 | }
210 |
211 | // PUBSUB NUMPAT
212 | $result = $this->redis->pubsub("somekey", "numpat");
213 | $this->assertTrue(is_int($result));
214 |
215 | // Invalid call
216 | $this->assertFalse($this->redis->pubsub("somekey", "notacommand"));
217 | }
218 |
219 | /* Unlike Redis proper, MsetNX won't always totally fail if all keys can't
220 | * be set, but rather will only fail per-node when that is the case */
221 | public function testMSetNX() {
222 | /* All of these keys should get set */
223 | $this->redis->del('x','y','z');
224 | $ret = $this->redis->msetnx(Array('x'=>'a','y'=>'b','z'=>'c'));
225 | $this->assertTrue(is_array($ret));
226 | $this->assertEquals(array_sum($ret),count($ret));
227 |
228 | /* Delete one key */
229 | $this->redis->del('x');
230 | $ret = $this->redis->msetnx(Array('x'=>'a','y'=>'b','z'=>'c'));
231 | $this->assertTrue(is_array($ret));
232 | $this->assertEquals(array_sum($ret),1);
233 |
234 | $this->assertFalse($this->redis->msetnx(array())); // set ø → FALSE
235 | }
236 |
237 | /* Slowlog needs to take a key or Array(ip, port), to direct it to a node */
238 | public function testSlowlog() {
239 | $str_key = uniqid() . '-' . rand(1, 1000);
240 |
241 | $this->assertTrue(is_array($this->redis->slowlog($str_key, 'get')));
242 | $this->assertTrue(is_array($this->redis->slowlog($str_key, 'get', 10)));
243 | $this->assertTrue(is_int($this->redis->slowlog($str_key, 'len')));
244 | $this->assertTrue($this->redis->slowlog($str_key, 'reset'));
245 | $this->assertFalse($this->redis->slowlog($str_key, 'notvalid'));
246 | }
247 |
248 | /* INFO COMMANDSTATS requires a key or ip:port for node direction */
249 | public function testInfoCommandStats() {
250 | $str_key = uniqid() . '-' . rand(1,1000);
251 | $arr_info = $this->redis->info($str_key, "COMMANDSTATS");
252 |
253 | $this->assertTrue(is_array($arr_info));
254 | if (is_array($arr_info)) {
255 | foreach($arr_info as $k => $str_value) {
256 | $this->assertTrue(strpos($k, 'cmdstat_') !== false);
257 | }
258 | }
259 | }
260 |
261 | /* RedisCluster will always respond with an array, even if transactions
262 | * failed, because the commands could be coming from multiple nodes */
263 | public function testFailedTransactions() {
264 | $this->redis->set('x', 42);
265 |
266 | // failed transaction
267 | $this->redis->watch('x');
268 |
269 | $r = $this->newInstance(); // new instance, modifying `x'.
270 | $r->incr('x');
271 |
272 | // This transaction should fail because the other client changed 'x'
273 | $ret = $this->redis->multi()->get('x')->exec();
274 | $this->assertTrue($ret === Array(FALSE));
275 | // watch and unwatch
276 | $this->redis->watch('x');
277 | $r->incr('x'); // other instance
278 | $this->redis->unwatch('x'); // cancel transaction watch
279 |
280 | // This should succeed as the watch has been cancelled
281 | $ret = $this->redis->multi()->get('x')->exec();
282 | $this->assertTrue($ret === array('44'));
283 | }
284 |
285 | /* RedisCluster::script() is a 'raw' command, which requires a key such that
286 | * we can direct it to a given node */
287 | public function testScript() {
288 | $str_key = uniqid() . '-' . rand(1,1000);
289 |
290 | // Flush any scripts we have
291 | $this->assertTrue($this->redis->script($str_key, 'flush'));
292 |
293 | // Silly scripts to test against
294 | $s1_src = 'return 1';
295 | $s1_sha = sha1($s1_src);
296 | $s2_src = 'return 2';
297 | $s2_sha = sha1($s2_src);
298 | $s3_src = 'return 3';
299 | $s3_sha = sha1($s3_src);
300 |
301 | // None should exist
302 | $result = $this->redis->script($str_key, 'exists', $s1_sha, $s2_sha, $s3_sha);
303 | $this->assertTrue(is_array($result) && count($result) == 3);
304 | $this->assertTrue(is_array($result) && count(array_filter($result)) == 0);
305 |
306 | // Load them up
307 | $this->assertTrue($this->redis->script($str_key, 'load', $s1_src) == $s1_sha);
308 | $this->assertTrue($this->redis->script($str_key, 'load', $s2_src) == $s2_sha);
309 | $this->assertTrue($this->redis->script($str_key, 'load', $s3_src) == $s3_sha);
310 |
311 | // They should all exist
312 | $result = $this->redis->script($str_key, 'exists', $s1_sha, $s2_sha, $s3_sha);
313 | $this->assertTrue(is_array($result) && count(array_filter($result)) == 3);
314 | }
315 |
316 | /* RedisCluster::EVALSHA needs a 'key' to let us know which node we want to
317 | * direct the command at */
318 | public function testEvalSHA() {
319 | $str_key = uniqid() . '-' . rand(1,1000);
320 |
321 | // Flush any loaded scripts
322 | $this->redis->script($str_key, 'flush');
323 |
324 | // Non existant script (but proper sha1), and a random (not) sha1 string
325 | $this->assertFalse($this->redis->evalsha(sha1(uniqid()),Array($str_key), 1));
326 | $this->assertFalse($this->redis->evalsha('some-random-data'),Array($str_key), 1);
327 |
328 | // Load a script
329 | $cb = uniqid(); // To ensure the script is new
330 | $scr = "local cb='$cb' return 1";
331 | $sha = sha1($scr);
332 |
333 | // Run it when it doesn't exist, run it with eval, and then run it with sha1
334 | $this->assertTrue(false === $this->redis->evalsha($scr,Array($str_key), 1));
335 | $this->assertTrue(1 === $this->redis->eval($scr,Array($str_key), 1));
336 | $this->assertTrue(1 === $this->redis->evalsha($sha,Array($str_key), 1));
337 | }
338 |
339 | /* Cluster specific introspection stuff */
340 | public function testIntrospection() {
341 | $arr_masters = $this->redis->_masters();
342 | $this->assertTrue(is_array($arr_masters));
343 |
344 | foreach ($arr_masters as $arr_info) {
345 | $this->assertTrue(is_array($arr_info));
346 | $this->assertTrue(is_string($arr_info[0]));
347 | $this->assertTrue(is_long($arr_info[1]));
348 | }
349 | }
350 |
351 | protected function genKeyName($i_key_idx, $i_type) {
352 | switch ($i_type) {
353 | case Redis::REDIS_STRING:
354 | return "string-$i_key_idx";
355 | case Redis::REDIS_SET:
356 | return "set-$i_key_idx";
357 | case Redis::REDIS_LIST:
358 | return "list-$i_key_idx";
359 | case Redis::REDIS_ZSET:
360 | return "zset-$i_key_idx";
361 | case Redis::REDIS_HASH:
362 | return "hash-$i_key_idx";
363 | default:
364 | return "unknown-$i_key_idx";
365 | }
366 | }
367 |
368 | protected function setKeyVals($i_key_idx, $i_type, &$arr_ref) {
369 | $str_key = $this->genKeyName($i_key_idx, $i_type);
370 |
371 | $this->redis->del($str_key);
372 |
373 | switch ($i_type) {
374 | case Redis::REDIS_STRING:
375 | $value = "$str_key-value";
376 | $this->redis->set($str_key, $value);
377 | break;
378 | case Redis::REDIS_SET:
379 | $value = Array(
380 | $str_key . '-mem1', $str_key . '-mem2', $str_key . '-mem3',
381 | $str_key . '-mem4', $str_key . '-mem5', $str_key . '-mem6'
382 | );
383 | $arr_args = $value;
384 | array_unshift($arr_args, $str_key);
385 | call_user_func_array(Array($this->redis, 'sadd'), $arr_args);
386 | break;
387 | case Redis::REDIS_HASH:
388 | $value = Array(
389 | $str_key . '-mem1' => $str_key . '-val1',
390 | $str_key . '-mem2' => $str_key . '-val2',
391 | $str_key . '-mem3' => $str_key . '-val3'
392 | );
393 | $this->redis->hmset($str_key, $value);
394 | break;
395 | case Redis::REDIS_LIST:
396 | $value = Array(
397 | $str_key . '-ele1', $str_key . '-ele2', $str_key . '-ele3',
398 | $str_key . '-ele4', $str_key . '-ele5', $str_key . '-ele6'
399 | );
400 | $arr_args = $value;
401 | array_unshift($arr_args, $str_key);
402 | call_user_func_array(Array($this->redis, 'rpush'), $arr_args);
403 | break;
404 | case Redis::REDIS_ZSET:
405 | $i_score = 1;
406 | $value = Array(
407 | $str_key . '-mem1' => 1, $str_key . '-mem2' => 2,
408 | $str_key . '-mem3' => 3, $str_key . '-mem3' => 3
409 | );
410 | foreach ($value as $str_mem => $i_score) {
411 | $this->redis->zadd($str_key, $i_score, $str_mem);
412 | }
413 | break;
414 | }
415 |
416 | /* Update our reference array so we can verify values */
417 | $arr_ref[$str_key] = $value;
418 | return $str_key;
419 | }
420 |
421 | /* Verify that our ZSET values are identical */
422 | protected function checkZSetEquality($a, $b) {
423 | /* If the count is off, the array keys are different or the sums are
424 | * different, we know there is something off */
425 | $boo_diff = count($a) != count($b) ||
426 | count(array_diff(array_keys($a), array_keys($b))) != 0 ||
427 | array_sum($a) != array_sum($b);
428 |
429 | if ($boo_diff) {
430 | $this->assertEquals($a,$b);
431 | return;
432 | }
433 | }
434 |
435 | protected function checkKeyValue($str_key, $i_type, $value) {
436 | switch ($i_type) {
437 | case Redis::REDIS_STRING:
438 | $this->assertEquals($value, $this->redis->get($str_key));
439 | break;
440 | case Redis::REDIS_SET:
441 | $arr_r_values = $this->redis->sMembers($str_key);
442 | $arr_l_values = $value;
443 | sort($arr_r_values);
444 | sort($arr_l_values);
445 | $this->assertEquals($arr_r_values, $arr_l_values);
446 | break;
447 | case Redis::REDIS_LIST:
448 | $this->assertEquals($value, $this->redis->lrange($str_key,0,-1));
449 | break;
450 | case Redis::REDIS_HASH:
451 | $this->assertEquals($value, $this->redis->hgetall($str_key));
452 | break;
453 | case Redis::REDIS_ZSET:
454 | $this->checkZSetEquality($value, $this->redis->zrange($str_key,0,-1,true));
455 | break;
456 | default:
457 | throw new Exception("Unknown type " . $i_type);
458 | }
459 | }
460 |
461 | /* Test automatic load distributor */
462 | public function testFailOver() {
463 | $arr_value_ref = Array();
464 | $arr_type_ref = Array();
465 |
466 | /* Set a bunch of keys of various redis types*/
467 | for ($i = 0; $i < 200; $i++) {
468 | foreach ($this->_arr_redis_types as $i_type) {
469 | $str_key = $this->setKeyVals($i, $i_type, $arr_value_ref);
470 | $arr_type_ref[$str_key] = $i_type;
471 | }
472 | }
473 |
474 | /* Iterate over failover options */
475 | foreach ($this->_arr_failover_types as $i_opt) {
476 | $this->redis->setOption(RedisCluster::OPT_SLAVE_FAILOVER, $i_opt);
477 |
478 | foreach ($arr_value_ref as $str_key => $value) {
479 | $this->checkKeyValue($str_key, $arr_type_ref[$str_key], $value);
480 | }
481 |
482 | break;
483 | }
484 | }
485 | }
486 | ?>
487 |
--------------------------------------------------------------------------------
/tests/TestRedis.php:
--------------------------------------------------------------------------------
1 |
57 |
--------------------------------------------------------------------------------
/tests/TestSuite.php:
--------------------------------------------------------------------------------
1 | assertTrue(!$bool);
47 | }
48 |
49 | protected function assertTrue($bool) {
50 | if($bool)
51 | return;
52 |
53 | $bt = debug_backtrace(false);
54 | self::$errors []= sprintf("Assertion failed: %s:%d (%s)\n",
55 | $bt[0]["file"], $bt[0]["line"], $bt[1]["function"]);
56 | }
57 |
58 | protected function assertLess($a, $b) {
59 | if($a < $b)
60 | return;
61 |
62 | $bt = debug_backtrace(false);
63 | self::$errors[] = sprintf("Assertion failed (%s >= %s): %s: %d (%s\n",
64 | print_r($a, true), print_r($b, true),
65 | $bt[0]["file"], $bt[0]["line"], $bt[1]["function"]);
66 | }
67 |
68 | protected function assertEquals($a, $b) {
69 | if($a === $b)
70 | return;
71 |
72 | $bt = debug_backtrace(false);
73 | self::$errors []= sprintf("Assertion failed (%s !== %s): %s:%d (%s)\n",
74 | print_r($a, true), print_r($b, true),
75 | $bt[0]["file"], $bt[0]["line"], $bt[1]["function"]);
76 | }
77 |
78 | protected function markTestSkipped($msg='') {
79 | $bt = debug_backtrace(false);
80 | self::$warnings []= sprintf("Skipped test: %s:%d (%s) %s\n",
81 | $bt[0]["file"], $bt[0]["line"], $bt[1]["function"], $msg);
82 |
83 | throw new Exception($msg);
84 | }
85 |
86 | private static function getMaxTestLen($arr_methods, $str_limit) {
87 | $i_result = 0;
88 |
89 | $str_limit = strtolower($str_limit);
90 | foreach ($arr_methods as $obj_method) {
91 | $str_name = strtolower($obj_method->name);
92 |
93 | if (substr($str_name, 0, 4) != 'test')
94 | continue;
95 | if ($str_limit && !strstr($str_name, $str_limit))
96 | continue;
97 |
98 | if (strlen($str_name) > $i_result) {
99 | $i_result = strlen($str_name);
100 | }
101 | }
102 | return $i_result;
103 | }
104 |
105 | /* Flag colorization */
106 | public static function flagColorization($boo_override) {
107 | self::$_boo_colorize = $boo_override && function_exists('posix_isatty') &&
108 | posix_isatty(STDOUT);
109 | }
110 |
111 | public static function run($className, $str_limit = NULL) {
112 | /* Lowercase our limit arg if we're passed one */
113 | $str_limit = $str_limit ? strtolower($str_limit) : $str_limit;
114 |
115 | $rc = new ReflectionClass($className);
116 | $methods = $rc->GetMethods(ReflectionMethod::IS_PUBLIC);
117 |
118 | $i_max_len = self::getMaxTestLen($methods, $str_limit);
119 |
120 | foreach($methods as $m) {
121 | $name = $m->name;
122 | if(substr($name, 0, 4) !== 'test')
123 | continue;
124 |
125 | /* If we're trying to limit to a specific test and can't match the
126 | * substring, skip */
127 | if ($str_limit && strstr(strtolower($name), $str_limit)===FALSE) {
128 | continue;
129 | }
130 |
131 | $str_out_name = str_pad($name, $i_max_len + 1);
132 | echo self::make_bold($str_out_name);
133 |
134 | $count = count($className::$errors);
135 | $rt = new $className();
136 | try {
137 | $rt->setUp();
138 | $rt->$name();
139 |
140 | if ($count === count($className::$errors)) {
141 | $str_msg = self::make_success('PASSED');
142 | } else {
143 | $str_msg = self::make_fail('FAILED');
144 | }
145 | //echo ($count === count($className::$errors)) ? "." : "F";
146 | } catch (Exception $e) {
147 | if ($e instanceof RedisException) {
148 | $className::$errors[] = "Uncaught exception '".$e->getMessage()."' ($name)\n";
149 | $str_msg = self::make_fail('FAILED');
150 | //echo 'F';
151 | } else {
152 | $str_msg = self::make_warning('SKIPPED');
153 | //echo 'S';
154 | }
155 | }
156 |
157 | echo "[" . $str_msg . "]\n";
158 | }
159 | echo "\n";
160 | echo implode('', $className::$warnings) . "\n";
161 |
162 | if(empty($className::$errors)) {
163 | echo "All tests passed. \o/\n";
164 | return 0;
165 | }
166 |
167 | echo implode('', $className::$errors);
168 | return 1;
169 | }
170 | }
171 |
172 | ?>
173 |
--------------------------------------------------------------------------------
/tests/make-cluster.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # make-cluster.sh
4 | # This is a simple script used to automatically spin up a Redis cluster instance
5 | # simplifying the process of running unit tests.
6 | #
7 | # Usage:
8 | # ./make-cluster.sh start
9 | # ./make-cluster.sh stop
10 | #
11 |
12 | BASEDIR=`dirname $@`
13 | NODEDIR=$BASEDIR/nodes
14 | MAPFILE=$NODEDIR/nodemap
15 |
16 | # Nodes, replicas, ports, etc. Change if you want different values
17 | NODES=12
18 | REPLICAS=3
19 | START_PORT=7000
20 | END_PORT=`expr $START_PORT + $NODES`
21 |
22 | # Helper to determine if we have an executable
23 | checkExe() {
24 | if ! hash $1 > /dev/null 2>&1; then
25 | echo "Error: Must have $1 on the path!"
26 | exit 1
27 | fi
28 | }
29 |
30 | # Run a command and output what we're running
31 | verboseRun() {
32 | echo "Running: $@"
33 | $@
34 | }
35 |
36 | # Spawn a specific redis instance, cluster enabled
37 | spawnNode() {
38 | # Attempt to spawn the node
39 | verboseRun redis-server --cluster-enabled yes --dir $NODEDIR --port $PORT \
40 | --cluster-config-file node-$PORT.conf --daemonize yes --save \'\' \
41 | --bind '127.0.0.1' --dbfilename node-$PORT.rdb
42 |
43 | # Abort if we can't spin this instance
44 | if [ $? -ne 0 ]; then
45 | echo "Error: Can't spawn node at port $PORT."
46 | exit 1
47 | fi
48 | }
49 |
50 | # Spawn nodes from start to end port
51 | spawnNodes() {
52 | for PORT in `seq $START_PORT $END_PORT`; do
53 | # Attempt to spawn the node
54 | spawnNode $PORT
55 |
56 | # Add this host:port to our nodemap so the tests can get seeds
57 | echo "127.0.0.1:$PORT" >> $MAPFILE
58 | done
59 | }
60 |
61 | # Check to see if any nodes are running
62 | checkNodes() {
63 | echo -n "Checking port availability "
64 |
65 | for PORT in `seq $START_PORT $END_PORT`; do
66 | redis-cli -p $PORT ping > /dev/null 2>&1
67 | if [ $? -eq 0 ]; then
68 | echo "FAIL"
69 | echo "Error: There appears to be an instance running at port $PORT"
70 | exit 1
71 | fi
72 | done
73 |
74 | echo "OK"
75 | }
76 |
77 | # Create our 'node' directory if it doesn't exist and clean out any previous
78 | # configuration files from a previous run.
79 | cleanConfigInfo() {
80 | verboseRun mkdir -p $NODEDIR
81 | verboseRun rm -f $NODEDIR/*
82 | }
83 |
84 | # Initialize our cluster with redis-trib.rb
85 | initCluster() {
86 | TRIBARGS=""
87 | for PORT in `seq $START_PORT $END_PORT`; do
88 | TRIBARGS="$TRIBARGS 127.0.0.1:$PORT"
89 | done
90 |
91 | verboseRun redis-trib.rb create --replicas $REPLICAS $TRIBARGS
92 |
93 | if [ $? -ne 0 ]; then
94 | echo "Error: Couldn't create cluster!"
95 | exit 1
96 | fi
97 | }
98 |
99 | # Attempt to spin up our cluster
100 | startCluster() {
101 | # Make sure none of these nodes are already running
102 | checkNodes
103 |
104 | # Clean out node configuration, etc
105 | cleanConfigInfo
106 |
107 | # Attempt to spawn the nodes
108 | spawnNodes
109 |
110 | # Attempt to initialize the cluster
111 | initCluster
112 | }
113 |
114 | # Shut down nodes in our cluster
115 | stopCluster() {
116 | for PORT in `seq $START_PORT $END_PORT`; do
117 | verboseRun redis-cli -p $PORT SHUTDOWN NOSAVE > /dev/null 2>&1
118 | done
119 | }
120 |
121 | # Make sure we have redis-server and redis-trib.rb on the path
122 | checkExe redis-server
123 | checkExe redis-trib.rb
124 |
125 | # Main entry point to start or stop/kill a cluster
126 | case "$1" in
127 | start)
128 | startCluster
129 | ;;
130 | stop)
131 | stopCluster
132 | ;;
133 | *)
134 | echo "Usage $0 [start|stop]"
135 | ;;
136 | esac
137 |
--------------------------------------------------------------------------------
/tests/mkring.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | PORTS="6379 6380 6381 6382"
4 | REDIS=redis-server
5 |
6 | function start_node() {
7 | P=$1
8 | echo "starting node on port $P";
9 | CONFIG_FILE=`tempfile`
10 | cat > $CONFIG_FILE << CONFIG
11 | port $P
12 | CONFIG
13 | $REDIS $CONFIG_FILE > /dev/null 2>/dev/null &
14 | sleep 1
15 | rm -f $CONFIG_FILE
16 | }
17 |
18 | function stop_node() {
19 |
20 | P=$1
21 | PID=$2
22 | redis-cli -h localhost -p $P shutdown
23 | kill -9 $PID 2>/dev/null
24 | }
25 |
26 | function stop() {
27 | for P in $PORTS; do
28 | PID=`lsof -i :$P | tail -1 | cut -f 2 -d " "`
29 | if [ "$PID" != "" ]; then
30 | stop_node $P $PID
31 | fi
32 | done
33 | }
34 |
35 | function start() {
36 | for P in $PORTS; do
37 | start_node $P
38 | done
39 | }
40 |
41 | case "$1" in
42 | start)
43 | start
44 | ;;
45 | stop)
46 | stop
47 | ;;
48 | restart)
49 | stop
50 | start
51 | ;;
52 | *)
53 | echo "Usage: $0 [start|stop|restart]"
54 | ;;
55 | esac
56 |
--------------------------------------------------------------------------------