├── .gitignore ├── COPYING ├── CREDITS ├── README.markdown ├── arrays.markdown ├── bybits.php ├── cluster.markdown ├── cluster_library.c ├── cluster_library.h ├── common.h ├── config.m4 ├── config.w32 ├── crc16.h ├── debian.control ├── debian ├── changelog ├── compat ├── control ├── copyright ├── postinst ├── postrm └── rules ├── library.c ├── library.h ├── ltmain.sh ├── mkdeb-apache2.sh ├── mkdeb.sh ├── package.xml ├── php_redis.h ├── redis.c ├── redis_array.c ├── redis_array.h ├── redis_array_impl.c ├── redis_array_impl.h ├── redis_cluster.c ├── redis_cluster.h ├── redis_commands.c ├── redis_commands.h ├── redis_session.c ├── redis_session.h ├── rpm ├── php-redis.spec └── redis.ini ├── serialize.list └── tests ├── RedisArrayTest.php ├── RedisClusterTest.php ├── RedisTest.php ├── TestRedis.php ├── TestSuite.php ├── make-cluster.sh └── mkring.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.deps 2 | *.libs 3 | Makefile* 4 | ac*.m4 5 | config.* 6 | *.o 7 | install-sh 8 | libtool 9 | ./*.sh 10 | configure* 11 | *.lo 12 | build* 13 | missing 14 | autom4te.cache 15 | mkinstalldirs 16 | run-tests.php 17 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------- 2 | The PHP License, version 3.01 3 | Copyright (c) 1999 - 2010 The PHP Group. All rights reserved. 4 | -------------------------------------------------------------------- 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, is permitted provided that the following conditions 8 | are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in 15 | the documentation and/or other materials provided with the 16 | distribution. 17 | 18 | 3. The name "PHP" must not be used to endorse or promote products 19 | derived from this software without prior written permission. For 20 | written permission, please contact group@php.net. 21 | 22 | 4. Products derived from this software may not be called "PHP", nor 23 | may "PHP" appear in their name, without prior written permission 24 | from group@php.net. You may indicate that your software works in 25 | conjunction with PHP by saying "Foo for PHP" instead of calling 26 | it "PHP Foo" or "phpfoo" 27 | 28 | 5. The PHP Group may publish revised and/or new versions of the 29 | license from time to time. Each version will be given a 30 | distinguishing version number. 31 | Once covered code has been published under a particular version 32 | of the license, you may always continue to use it under the terms 33 | of that version. You may also choose to use such covered code 34 | under the terms of any subsequent version of the license 35 | published by the PHP Group. No one other than the PHP Group has 36 | the right to modify the terms applicable to covered code created 37 | under this License. 38 | 39 | 6. Redistributions of any form whatsoever must retain the following 40 | acknowledgment: 41 | "This product includes PHP software, freely available from 42 | ". 43 | 44 | THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND 45 | ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 46 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 47 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP 48 | DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 49 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 50 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 51 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 52 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 53 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 54 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 55 | OF THE POSSIBILITY OF SUCH DAMAGE. 56 | 57 | -------------------------------------------------------------------- 58 | 59 | This software consists of voluntary contributions made by many 60 | individuals on behalf of the PHP Group. 61 | 62 | The PHP Group can be contacted via Email at group@php.net. 63 | 64 | For more information on the PHP Group and the PHP project, 65 | please see . 66 | 67 | PHP includes the Zend Engine, freely available at 68 | . 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 | --------------------------------------------------------------------------------