); // Kill the process at ip:port
~~~
##### *Return value*
This will vary depending on which client command was executed.
* CLIENT LIST will return an array of arrays with client information.
* CLIENT GETNAME will return the client name or false if none has been set
* CLIENT SETNAME will return true if it can be set and false if not
* CLIENT KILL will return true if the client can be killed, and false if not
Note: phpredis will attempt to reconnect so you can actually kill your own connection
but may not notice losing it!
### getLastError
-----
_**Description**_: The last error message (if any)
##### *Parameters*
*none*
##### *Return value*
A string with the last returned script based error message, or NULL if there is no error
##### *Examples*
~~~
$redis->eval('this-is-not-lua');
$err = $redis->getLastError();
// "ERR Error compiling script (new function): user_script:1: '=' expected near '-'"
~~~
### clearLastError
-----
_**Description**_: Clear the last error message
##### *Parameters*
*none*
##### *Return value*
*BOOL* TRUE
##### *Examples*
~~~
$redis->set('x', 'a');
$redis->incr('x');
$err = $redis->getLastError();
// "ERR value is not an integer or out of range"
$redis->clearLastError();
$err = $redis->getLastError();
// NULL
~~~
### _prefix
-----
_**Description**_: A utility method to prefix the value with the prefix setting for phpredis.
##### *Parameters*
*value* string. The value you wish to prefix
##### *Return value*
If a prefix is set up, the value now prefixed. If there is no prefix, the value will be returned unchanged.
##### *Examples*
~~~
$redis->setOption(Redis::OPT_PREFIX, 'my-prefix:');
$redis->_prefix('my-value'); // Will return 'my-prefix:my-value'
~~~
### _serialize
-----
_**Description**_: A utility method to serialize values manually.
This method allows you to serialize a value with whatever serializer is configured, manually.
This can be useful for serialization/unserialization of data going in and out of EVAL commands
as phpredis can't automatically do this itself. Note that if no serializer is set, phpredis
will change Array values to 'Array', and Objects to 'Object'.
##### *Parameters*
*value*: Mixed. The value to be serialized
##### *Examples*
~~~
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE);
$redis->_serialize("foo"); // returns "foo"
$redis->_serialize(Array()); // Returns "Array"
$redis->_serialize(new stdClass()); // Returns "Object"
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
$redis->_serialize("foo"); // Returns 's:3:"foo";'
~~~
### _unserialize
-----
_**Description**_: A utility method to unserialize data with whatever serializer is set up.
If there is no serializer set, the value will be returned unchanged. If there is a serializer set up,
and the data passed in is malformed, an exception will be thrown. This can be useful if phpredis is
serializing values, and you return something from redis in a LUA script that is serialized.
##### *Parameters*
*value* string. The value to be unserialized
##### *Examples*
~~~
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
$redis->_unserialize('a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}'); // Will return Array(1,2,3)
~~~
## Introspection
### isConnected
-----
_**Description**_: A method to determine if a phpredis object thinks it's connected to a server
##### *Parameters*
None
##### *Return value*
*Boolean* Returns TRUE if phpredis thinks it's connected and FALSE if not
### getHost
-----
_**Description**_: Retreive our host or unix socket that we're connected to
##### *Parameters*
None
##### *Return value*
*Mixed* The host or unix socket we're connected to or FALSE if we're not connected
### getPort
-----
_**Description**_: Get the port we're connected to
##### *Parameters*
None
##### *Return value*
*Mixed* Returns the port we're connected to or FALSE if we're not connected
### getDbNum
-----
_**Description**_: Get the database number phpredis is pointed to
##### *Parameters*
None
##### *Return value*
*Mixed* Returns the database number (LONG) phpredis thinks it's pointing to or FALSE if we're not connected
### getTimeout
-----
_**Description**_: Get the (write) timeout in use for phpredis
##### *Parameters*
None
##### *Return value*
*Mixed* The timeout (DOUBLE) specified in our connect call or FALSE if we're not connected
### getReadTimeout
_**Description**_: Get the read timeout specified to phpredis or FALSE if we're not connected
##### *Parameters*
None
##### *Return value*
*Mixed* Returns the read timeout (which can be set using setOption and Redis::OPT_READ_TIMEOUT) or FALSE if we're not connected
### getPersistentID
-----
_**Description**_: Gets the persistent ID that phpredis is using
##### *Parameters*
None
##### *Return value*
*Mixed* Returns the persistent id phpredis is using (which will only be set if connected with pconnect), NULL if we're not
using a persistent ID, and FALSE if we're not connected
### getAuth
-----
_**Description**_: Get the password used to authenticate the phpredis connection
### *Parameters*
None
### *Return value*
*Mixed* Returns the password used to authenticate a phpredis session or NULL if none was used, and FALSE if we're not connected
redis-3.1.6/arrays.markdown 0000644 0001750 0000012 00000021064 13223116600 016450 0 ustar pyatsukhnenko wheel Redis Arrays
============
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/).**
An array is composed of the following:
* A list of Redis hosts.
* 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.
* 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.
* 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.
* An option to rehash the array automatically as nodes are added or removed, set by the "autorehash" option.
## Creating an array
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);`
#### Declaring a new array with a list of nodes
$ra = new RedisArray(array("host1", "host2:63792", "host2:6380"));
#### Declaring a new array with a list of nodes and a function to extract a part of the key
function extract_key_part($k) {
return substr($k, 0, 3); // hash only on first 3 characters.
}
$ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("function" => "extract_key_part"));
#### Defining a "previous" array when nodes are added or removed.
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.
// 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.
$ra = new RedisArray(array("host1", "host2", "host3"), array("previous" => array("host1", "host2")));
#### Specifying the "retry_interval" parameter
The retry_interval is used to specify a delay in milliseconds between reconnection attempts in case the client loses connection with a server
$ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("retry_timeout" => 100));
#### Specifying the "lazy_connect" parameter
This option is useful when a cluster has many shards but not of them are necessarily used at one time.
$ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("lazy_connect" => true));
#### Specifying the "connect_timeout" parameter
The connect_timeout value is a double and is used to specify a timeout in number of seconds when creating redis socket connections used in the RedisArray.
$ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("connect_timeout" => 0.5));
#### Specifying the "read_timeout" parameter
The read_timeout value is a double and is used to specify a timeout in number of seconds when waiting response from the server.
$ra = new RedisArray(array("host1", "host2:63792", "host2:6380"), array("read_timeout" => 0.5));
#### Defining arrays in Redis.ini
Because php.ini parameters must be pre-defined, Redis Arrays must all share the same .ini settings.
// list available Redis Arrays
ini_set('redis.array.names', 'users,friends');
// set host names for each array.
ini_set('redis.arrays.hosts', 'users[]=localhost:6379&users[]=localhost:6380&users[]=localhost:6381&users[]=localhost:6382&friends[]=localhost');
// set functions
ini_set('redis.arrays.functions', 'users=user_hash');
// use index only for users
ini_set('redis.arrays.index', 'users=1,friends=0');
## Usage
Redis arrays can be used just as Redis objects:
$ra = new RedisArray("users");
$ra->set("user1:name", "Joe");
$ra->set("user2:name", "Mike");
## Key hashing
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.
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.
## Custom key distribution function
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.
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.
### Example
$ra = new RedisArray(array("host1", "host2", "host3", "host4", "host5", "host6", "host7", "host8"), array("distributor" => array(2, 2)));
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.
## Migrating keys
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){ ... });`.
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.
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.
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.
Adding and/or removing several instances is supported.
### Example
$ra = new RedisArray("users"); // load up a new config from redis.ini, using the “.previous” listing.
$ra->_rehash();
Running this code will:
* Create a new ring with the updated list of nodes.
* Server by server, look up all the keys in the previous list of nodes.
* Rehash each key and possibly move it to another server.
* Update the array object with the new list of nodes.
## Multi/exec
Multi/exec is still available, but must be run on a single node:
$host = $ra->_target("{users}:user1:name"); // find host first
$ra->multi($host) // then run transaction on that host.
->del("{users}:user1:name")
->srem("{users}:index", "user1")
->exec();
## Limitations
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.
## Array info
RedisArray objects provide several methods to help understand the state of the cluster. These methods start with an underscore.
* `$ra->_hosts()` → returns a list of hosts for the selected array.
* `$ra->_function()` → returns the name of the function used to extract key parts during consistent hashing.
* `$ra->_target($key)` → returns the host to be used for a certain key.
* `$ra->_instance($host)` → returns a redis instance connected to a specific node; use with `_target` to get a single Redis object.
## Running the unit tests
$ cd tests
$ ./mkring.sh start
$ php array-tests.php
redis-3.1.6/cluster.markdown 0000644 0001750 0000012 00000023602 13223116600 016630 0 ustar pyatsukhnenko wheel Redis Cluster
=============
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 [Tradesy](https://www.tradesy.com/)**
## Creating and connecting to a cluster
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.
#### Declaring a cluster with an array of seeds
// Create a cluster setting two nodes as seeds
$obj_cluster = new RedisCluster(NULL, Array('host:7000', 'host:7001', 'host:7003'));
// Connect and specify timeout and read_timeout
$obj_cluster = new RedisCluster(NULL, Array("host:7000", "host:7001"), 1.5, 1.5);
// Connect with read/write timeout as well as specify that phpredis should use
// persistent connections to each node.
$obj_cluster = new RedisCluster(NULL, Array("host:7000", "host:7001"), 1.5, 1.5, true);
#### Loading a cluster configuration by name
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.
# In redis.ini
redis.clusters.seeds = "mycluster[]=localhost:7000&test[]=localhost:7001"
redis.clusters.timeout = "mycluster=5"
redis.clusters.read_timeout = "mycluster=10"
Then, this cluster can be loaded by doing the following
$obj_cluster = new RedisCluster('mycluster');
## Connection process
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.)
## Timeouts
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.
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.
## Keyspace map
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.
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.
## Automatic slave failover / distribution
By default, RedisCluster will only ever send commands to master nodes, but can be configured differently for readonly commands if requested.
// The default option, only send commands to master nodes
$obj_cluster->setOption(RedisCluster::OPT_SLAVE_FAILOVER, RedisCluster::FAILOVER_NONE);
// In the event we can't reach a master, and it has slaves, failover for read commands
$obj_cluster->setOption(RedisCluster::OPT_SLAVE_FAILOVER, RedisCluster::FAILOVER_ERROR);
// Always distribute readonly commands between masters and slaves, at random
$obj_cluster->setOption(
RedisCluster::OPT_SLAVE_FAILOVER, RedisCluster::FAILOVER_DISTRIBUTE
);
## Main command loop
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:
1. We fail to communicate with *any* node that we are aware of, in which case a ```RedisClusterExecption``` is raised.
2. We have been bounced around longer than the timeout which was set on construction.
3. Redis cluster returns us a ```CLUSTERDOWN``` error, in which case a ```RedisClusterException``` is raised.
4. We receive a valid response, in which case the data is returned to the caller.
## Transactions
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.
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.
Consider the following example:
// Cluster is put into MULTI state locally
$obj_cluster->multi();
// The cluster will issue MULTI on this node first (and only once)
$obj_cluster->get("mykey");
$obj_cluster->set("mykey", "new_value");
// If 'myotherkey' maps to a different node, MULTI will be issued there
// before requesting the key
$obj_cluster->get("myotherkey");
// This will always return an array, even in the event of a failed transaction
// on one of the nodes, in which case that element will be FALSE
print_r($obj_cluster->exec());
## Pipelining
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.
## Multiple key commands
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.
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.
### MGET and MSET
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.
// This will be delivered in two commands. First for all of the {hash1} keys,
// and then to grab 'otherkey'
$obj_cluster->mget(Array("{hash1}key1","{hash1}key2","{hash1}key3","otherkey"));
This operation can also be done in MULTI mode transparently.
## Directed node commands
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.
// This will be directed at the slot/node which would store "mykey"
$obj_cluster->echo("mykey","Hello World!");
// Here we're iterating all of our known masters, and delivering the command there
foreach ($obj_cluster->_masters() as $arr_master) {
$obj_cluster->echo($arr_master, "Hello: " . implode(':', $arr_master));
}
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:
1. SAVE
2. BGSAVE
3. FLUSHDB
4. FLUSHALL
5. DBSIZE
6. BGREWRITEAOF
7. LASTSAVE
8. INFO
9. CLIENT
10. CLUSTER
11. CONFIG
12. PUBSUB
13. SLOWLOG
14. RANDOMKEY
15. PING
## Session Handler
You can use the cluster functionality of phpredis to store PHP session information in a Redis cluster as you can with a non cluster-enabled Redis instance.
To do this, you must configure your `session.save_handler` and `session.save_path` INI variables to give phpredis enough information to communicate with the cluster.
~~~
session.save_handler = rediscluster
session.save_path = "seed[]=host1:port1&seed[]=host2:port2&seed[]=hostN:portN&timeout=2&read_timeout=2&failover=error&persistent=1"
~~~
### session.session_handler
Set this variable to "rediscluster" to inform phpredis that this is a cluster instance.
### session.save_path
The save path for cluster based session storage takes the form of a PHP GET request, and requires that you specify at least on `seed` node. Other options you can specify are as follows:
* _timeout (double)_: The amount of time phpredis will wait when connecting or writing to the cluster.
* _read_timeout (double)_: The amount of time phpredis will wait for a result from the cluster.
* _persistent_: Tells phpredis whether persistent connections should be used.
* _distribute_: phpredis will randomly distribute session reads between masters and any attached slaves (load balancing).
* _failover (string)_: How phpredis should distribute session reads between master and slave nodes.
* * _none_ : phpredis will only communicate with master nodes
* * _error_: phpredis will communicate with master nodes unless one failes, in which case an attempt will be made to read session information from a slave.
redis-3.1.6/CREDITS 0000644 0001750 0000012 00000000314 13223116600 014416 0 ustar pyatsukhnenko wheel Redis client extension for PHP
Alfonso Jimenez (yo@alfonsojimenez.com)
Nasreddine Bouafif (n.bouafif@owlient.eu)
Nicolas Favre-Felix (n.favre-felix@owlient.eu)
Michael Grunder (michael.grunder@gmail.com)
redis-3.1.6/COPYING 0000644 0001750 0000012 00000006222 13223116600 014435 0 ustar pyatsukhnenko wheel --------------------------------------------------------------------
The PHP License, version 3.01
Copyright (c) 1999 - 2010 The PHP Group. All rights reserved.
--------------------------------------------------------------------
Redistribution and use in source and binary forms, with or without
modification, is permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
3. The name "PHP" must not be used to endorse or promote products
derived from this software without prior written permission. For
written permission, please contact group@php.net.
4. Products derived from this software may not be called "PHP", nor
may "PHP" appear in their name, without prior written permission
from group@php.net. You may indicate that your software works in
conjunction with PHP by saying "Foo for PHP" instead of calling
it "PHP Foo" or "phpfoo"
5. The PHP Group may publish revised and/or new versions of the
license from time to time. Each version will be given a
distinguishing version number.
Once covered code has been published under a particular version
of the license, you may always continue to use it under the terms
of that version. You may also choose to use such covered code
under the terms of any subsequent version of the license
published by the PHP Group. No one other than the PHP Group has
the right to modify the terms applicable to covered code created
under this License.
6. Redistributions of any form whatsoever must retain the following
acknowledgment:
"This product includes PHP software, freely available from
".
THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND
ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP
DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------
This software consists of voluntary contributions made by many
individuals on behalf of the PHP Group.
The PHP Group can be contacted via Email at group@php.net.
For more information on the PHP Group and the PHP project,
please see .
PHP includes the Zend Engine, freely available at
.
redis-3.1.6/config.m4 0000755 0001750 0000012 00000006503 13223116600 015116 0 ustar pyatsukhnenko wheel dnl $Id$
dnl config.m4 for extension redis
PHP_ARG_ENABLE(redis, whether to enable redis support,
dnl Make sure that the comment is aligned:
[ --enable-redis Enable redis support])
PHP_ARG_ENABLE(redis-session, whether to enable sessions,
[ --disable-redis-session Disable session support], yes, no)
PHP_ARG_ENABLE(redis-igbinary, whether to enable igbinary serializer support,
[ --enable-redis-igbinary Enable igbinary serializer support], no, no)
if test "$PHP_REDIS" != "no"; then
if test "$PHP_REDIS_SESSION" != "no"; then
AC_DEFINE(PHP_SESSION,1,[redis sessions])
fi
dnl Check for igbinary
if test "$PHP_REDIS_IGBINARY" != "no"; then
AC_MSG_CHECKING([for igbinary includes])
igbinary_inc_path=""
if test -f "$abs_srcdir/include/php/ext/igbinary/igbinary.h"; then
igbinary_inc_path="$abs_srcdir/include/php"
elif test -f "$abs_srcdir/ext/igbinary/igbinary.h"; then
igbinary_inc_path="$abs_srcdir"
elif test -f "$phpincludedir/ext/igbinary/igbinary.h"; then
igbinary_inc_path="$phpincludedir"
else
for i in php php4 php5 php6; do
if test -f "$prefix/include/$i/ext/igbinary/igbinary.h"; then
igbinary_inc_path="$prefix/include/$i"
fi
done
fi
if test "$igbinary_inc_path" = ""; then
AC_MSG_ERROR([Cannot find igbinary.h])
else
AC_MSG_RESULT([$igbinary_inc_path])
fi
fi
AC_MSG_CHECKING([for redis igbinary support])
if test "$PHP_REDIS_IGBINARY" != "no"; then
AC_MSG_RESULT([enabled])
AC_DEFINE(HAVE_REDIS_IGBINARY,1,[Whether redis igbinary serializer is enabled])
IGBINARY_INCLUDES="-I$igbinary_inc_path"
IGBINARY_EXT_DIR="$igbinary_inc_path/ext"
ifdef([PHP_ADD_EXTENSION_DEP],
[
PHP_ADD_EXTENSION_DEP(redis, igbinary)
])
PHP_ADD_INCLUDE($IGBINARY_EXT_DIR)
else
IGBINARY_INCLUDES=""
AC_MSG_RESULT([disabled])
fi
dnl # --with-redis -> check with-path
dnl SEARCH_PATH="/usr/local /usr" # you might want to change this
dnl SEARCH_FOR="/include/redis.h" # you most likely want to change this
dnl if test -r $PHP_REDIS/$SEARCH_FOR; then # path given as parameter
dnl REDIS_DIR=$PHP_REDIS
dnl else # search default path list
dnl AC_MSG_CHECKING([for redis files in default path])
dnl for i in $SEARCH_PATH ; do
dnl if test -r $i/$SEARCH_FOR; then
dnl REDIS_DIR=$i
dnl AC_MSG_RESULT(found in $i)
dnl fi
dnl done
dnl fi
dnl
dnl if test -z "$REDIS_DIR"; then
dnl AC_MSG_RESULT([not found])
dnl AC_MSG_ERROR([Please reinstall the redis distribution])
dnl fi
dnl # --with-redis -> add include path
dnl PHP_ADD_INCLUDE($REDIS_DIR/include)
dnl # --with-redis -> check for lib and symbol presence
dnl LIBNAME=redis # you may want to change this
dnl LIBSYMBOL=redis # you most likely want to change this
dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL,
dnl [
dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $REDIS_DIR/lib, REDIS_SHARED_LIBADD)
dnl AC_DEFINE(HAVE_REDISLIB,1,[ ])
dnl ],[
dnl AC_MSG_ERROR([wrong redis lib version or lib not found])
dnl ],[
dnl -L$REDIS_DIR/lib -lm -ldl
dnl ])
dnl
dnl PHP_SUBST(REDIS_SHARED_LIBADD)
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)
fi
redis-3.1.6/config.w32 0000644 0001750 0000012 00000001633 13223116600 015205 0 ustar pyatsukhnenko wheel // vim: ft=javascript:
ARG_ENABLE("redis", "whether to enable redis support", "no");
ARG_ENABLE("redis-session", "whether to enable sessions", "yes");
ARG_ENABLE("redis-igbinary", "whether to enable igbinary serializer support", "no");
if (PHP_REDIS != "no") {
var sources = "redis.c redis_commands.c library.c redis_session.c redis_array.c redis_array_impl.c redis_cluster.c cluster_library.c";
if (PHP_REDIS_SESSION != "no") {
ADD_EXTENSION_DEP("redis", "session");
ADD_FLAG("CFLAGS_REDIS", ' /D PHP_SESSION=1 ');
AC_DEFINE("HAVE_REDIS_SESSION", 1);
}
if (PHP_REDIS_IGBINARY != "no") {
if (CHECK_HEADER_ADD_INCLUDE("igbinary.h", "CFLAGS_REDIS", configure_module_dirname + "\\..\\igbinary")) {
ADD_EXTENSION_DEP("redis", "igbinary");
AC_DEFINE("HAVE_REDIS_IGBINARY", 1);
} else {
WARNING("redis igbinary support not enabled");
}
}
EXTENSION("redis", sources);
}
redis-3.1.6/common.h 0000644 0001750 0000012 00000077013 13223116600 015051 0 ustar pyatsukhnenko wheel #include "php.h"
#include "php_ini.h"
#ifndef REDIS_COMMON_H
#define REDIS_COMMON_H
#define PHPREDIS_NOTUSED(v) ((void)v)
#include
#include
#if (PHP_MAJOR_VERSION < 7)
#include
typedef smart_str smart_string;
#define smart_string_0(x) smart_str_0(x)
#define smart_string_appendc(dest, c) smart_str_appendc(dest, c)
#define smart_string_append_long(dest, val) smart_str_append_long(dest, val)
#define smart_string_appendl(dest, src, len) smart_str_appendl(dest, src, len)
typedef struct {
short gc;
size_t len;
char *val;
} zend_string;
#define ZSTR_VAL(s) (s)->val
#define ZSTR_LEN(s) (s)->len
static zend_always_inline zend_string *
zend_string_init(const char *str, size_t len, int persistent)
{
zend_string *zstr = emalloc(sizeof(zend_string) + len + 1);
ZSTR_VAL(zstr) = (char *)zstr + sizeof(zend_string);
memcpy(ZSTR_VAL(zstr), str, len);
ZSTR_VAL(zstr)[len] = '\0';
zstr->len = len;
zstr->gc = 0x01;
return zstr;
}
#define zend_string_release(s) do { \
if ((s) && (s)->gc) { \
if ((s)->gc & 0x10 && ZSTR_VAL(s)) efree(ZSTR_VAL(s)); \
if ((s)->gc & 0x01) efree((s)); \
} \
} while (0)
#define ZEND_HASH_FOREACH_KEY_VAL(ht, _h, _key, _val) do { \
HashPosition _hpos; \
for (zend_hash_internal_pointer_reset_ex(ht, &_hpos); \
zend_hash_has_more_elements_ex(ht, &_hpos) == SUCCESS; \
zend_hash_move_forward_ex(ht, &_hpos) \
) { \
zend_string _zstr = {0}; \
char *_str_index; uint _str_length; ulong _num_index; \
_h = 0; _key = NULL; _val = zend_hash_get_current_data_ex(ht, &_hpos); \
switch (zend_hash_get_current_key_ex(ht, &_str_index, &_str_length, &_num_index, 0, &_hpos)) { \
case HASH_KEY_IS_STRING: \
_zstr.len = _str_length - 1; \
_zstr.val = _str_index; \
_key = &_zstr; \
break; \
case HASH_KEY_IS_LONG: \
_h = _num_index; \
break; \
default: \
/* noop */ break; \
}
#define ZEND_HASH_FOREACH_VAL(ht, _val) do { \
HashPosition _hpos; \
for (zend_hash_internal_pointer_reset_ex(ht, &_hpos); \
zend_hash_has_more_elements_ex(ht, &_hpos) == SUCCESS; \
zend_hash_move_forward_ex(ht, &_hpos) \
) { \
_val = zend_hash_get_current_data_ex(ht, &_hpos); \
#define ZEND_HASH_FOREACH_PTR(ht, _ptr) do { \
HashPosition _hpos; \
for (zend_hash_internal_pointer_reset_ex(ht, &_hpos); \
zend_hash_has_more_elements_ex(ht, &_hpos) == SUCCESS; \
zend_hash_move_forward_ex(ht, &_hpos) \
) { \
_ptr = zend_hash_get_current_data_ptr_ex(ht, &_hpos); \
#define ZEND_HASH_FOREACH_END() \
} \
} while(0)
#undef zend_hash_get_current_key
#define zend_hash_get_current_key(ht, str_index, num_index) \
zend_hash_get_current_key_ex(ht, str_index, NULL, num_index, 0, NULL)
#define zend_hash_str_exists(ht, str, len) zend_hash_exists(ht, str, len + 1)
static zend_always_inline zval *
zend_hash_str_find(const HashTable *ht, const char *key, size_t len)
{
zval **zv;
if (zend_hash_find(ht, key, len + 1, (void **)&zv) == SUCCESS) {
return *zv;
}
return NULL;
}
static zend_always_inline void *
zend_hash_str_find_ptr(const HashTable *ht, const char *str, size_t len)
{
void **ptr;
if (zend_hash_find(ht, str, len + 1, (void **)&ptr) == SUCCESS) {
return *ptr;
}
return NULL;
}
static zend_always_inline void *
zend_hash_str_update_ptr(HashTable *ht, const char *str, size_t len, void *pData)
{
if (zend_hash_update(ht, str, len + 1, (void *)&pData, sizeof(void *), NULL) == SUCCESS) {
return pData;
}
return NULL;
}
static zend_always_inline void *
zend_hash_index_update_ptr(HashTable *ht, zend_ulong h, void *pData)
{
if (zend_hash_index_update(ht, h, (void **)&pData, sizeof(void *), NULL) == SUCCESS) {
return pData;
}
return NULL;
}
#undef zend_hash_get_current_data
static zend_always_inline zval *
zend_hash_get_current_data(HashTable *ht)
{
zval **zv;
if (zend_hash_get_current_data_ex(ht, (void **)&zv, NULL) == SUCCESS) {
return *zv;
}
return NULL;
}
static zend_always_inline void *
zend_hash_get_current_data_ptr_ex(HashTable *ht, HashPosition *pos)
{
void **ptr;
if (zend_hash_get_current_data_ex(ht, (void **)&ptr, pos) == SUCCESS) {
return *ptr;
}
return NULL;
}
#define zend_hash_get_current_data_ptr(ht) zend_hash_get_current_data_ptr_ex(ht, NULL)
static int (*_zend_hash_index_find)(const HashTable *, ulong, void **) = &zend_hash_index_find;
#define zend_hash_index_find(ht, h) inline_zend_hash_index_find(ht, h)
static zend_always_inline zval *
inline_zend_hash_index_find(const HashTable *ht, zend_ulong h)
{
zval **zv;
if (_zend_hash_index_find(ht, h, (void **)&zv) == SUCCESS) {
return *zv;
}
return NULL;
}
static zend_always_inline void *
zend_hash_index_find_ptr(const HashTable *ht, zend_ulong h)
{
void **ptr;
if (_zend_hash_index_find(ht, h, (void **)&ptr) == SUCCESS) {
return *ptr;
}
return NULL;
}
static int (*_zend_hash_get_current_data_ex)(HashTable *, void **, HashPosition *) = &zend_hash_get_current_data_ex;
#define zend_hash_get_current_data_ex(ht, pos) inline_zend_hash_get_current_data_ex(ht, pos)
static zend_always_inline zval *
inline_zend_hash_get_current_data_ex(HashTable *ht, HashPosition *pos)
{
zval **zv;
if (_zend_hash_get_current_data_ex(ht, (void **)&zv, pos) == SUCCESS) {
return *zv;
}
return NULL;
}
#undef zend_hash_next_index_insert
#define zend_hash_next_index_insert(ht, pData) \
_zend_hash_next_index_insert(ht, pData ZEND_FILE_LINE_CC)
static zend_always_inline zval *
_zend_hash_next_index_insert(HashTable *ht, zval *pData ZEND_FILE_LINE_DC)
{
if (_zend_hash_index_update_or_next_insert(ht, 0, &pData, sizeof(pData),
NULL, HASH_NEXT_INSERT ZEND_FILE_LINE_CC) == SUCCESS
) {
return pData;
}
return NULL;
}
#undef zend_get_parameters_array
#define zend_get_parameters_array(ht, param_count, argument_array) \
inline_zend_get_parameters_array(ht, param_count, argument_array TSRMLS_CC)
static zend_always_inline int
inline_zend_get_parameters_array(int ht, int param_count, zval *argument_array TSRMLS_DC)
{
int i, ret = FAILURE;
zval **zv = ecalloc(param_count, sizeof(zval *));
if (_zend_get_parameters_array(ht, param_count, zv TSRMLS_CC) == SUCCESS) {
for (i = 0; i < param_count; i++) {
argument_array[i] = *zv[i];
}
ret = SUCCESS;
}
efree(zv);
return ret;
}
typedef zend_rsrc_list_entry zend_resource;
extern int (*_add_next_index_string)(zval *, const char *, int);
#define add_next_index_string(arg, str) _add_next_index_string(arg, str, 1);
extern int (*_add_next_index_stringl)(zval *, const char *, uint, int);
#define add_next_index_stringl(arg, str, length) _add_next_index_stringl(arg, str, length, 1);
#undef ZVAL_STRING
#define ZVAL_STRING(z, s) do { \
const char *_s = (s); \
ZVAL_STRINGL(z, _s, strlen(_s)); \
} while (0)
#undef RETVAL_STRING
#define RETVAL_STRING(s) ZVAL_STRING(return_value, s)
#undef RETURN_STRING
#define RETURN_STRING(s) { RETVAL_STRING(s); return; }
#undef ZVAL_STRINGL
#define ZVAL_STRINGL(z, s, l) do { \
const char *__s = (s); int __l = l; \
zval *__z = (z); \
Z_STRLEN_P(__z) = __l; \
Z_STRVAL_P(__z) = estrndup(__s, __l); \
Z_TYPE_P(__z) = IS_STRING; \
} while (0)
#undef RETVAL_STRINGL
#define RETVAL_STRINGL(s, l) ZVAL_STRINGL(return_value, s, l)
#undef RETURN_STRINGL
#define RETURN_STRINGL(s, l) { RETVAL_STRINGL(s, l); return; }
static int (*_call_user_function)(HashTable *, zval **, zval *, zval *, zend_uint, zval *[] TSRMLS_DC) = &call_user_function;
#define call_user_function(function_table, object, function_name, retval_ptr, param_count, params) \
inline_call_user_function(function_table, object, function_name, retval_ptr, param_count, params TSRMLS_CC)
static zend_always_inline int
inline_call_user_function(HashTable *function_table, zval *object, zval *function_name, zval *retval_ptr, zend_uint param_count, zval params[] TSRMLS_DC)
{
int i, ret;
zval **_params = NULL;
if (!params) param_count = 0;
if (param_count > 0) {
_params = ecalloc(param_count, sizeof(zval *));
for (i = 0; i < param_count; ++i) {
zval *zv = ¶ms[i];
MAKE_STD_ZVAL(_params[i]);
ZVAL_ZVAL(_params[i], zv, 1, 0);
}
}
ret = _call_user_function(function_table, &object, function_name, retval_ptr, param_count, _params TSRMLS_CC);
if (_params) {
for (i = 0; i < param_count; ++i) {
zval_ptr_dtor(&_params[i]);
}
efree(_params);
}
return ret;
}
#undef add_assoc_bool
#define add_assoc_bool(__arg, __key, __b) add_assoc_bool_ex(__arg, __key, strlen(__key), __b)
extern int (*_add_assoc_bool_ex)(zval *, const char *, uint, int);
#define add_assoc_bool_ex(_arg, _key, _key_len, _b) _add_assoc_bool_ex(_arg, _key, _key_len + 1, _b)
#undef add_assoc_long
#define add_assoc_long(__arg, __key, __n) add_assoc_long_ex(__arg, __key, strlen(__key), __n)
extern int (*_add_assoc_long_ex)(zval *, const char *, uint, long);
#define add_assoc_long_ex(_arg, _key, _key_len, _n) _add_assoc_long_ex(_arg, _key, _key_len + 1, _n)
#undef add_assoc_double
#define add_assoc_double(__arg, __key, __d) add_assoc_double_ex(__arg, __key, strlen(__key), __d)
extern int (*_add_assoc_double_ex)(zval *, const char *, uint, double);
#define add_assoc_double_ex(_arg, _key, _key_len, _d) _add_assoc_double_ex(_arg, _key, _key_len + 1, _d)
#undef add_assoc_string
#define add_assoc_string(__arg, __key, __str) add_assoc_string_ex(__arg, __key, strlen(__key), __str)
extern int (*_add_assoc_string_ex)(zval *, const char *, uint, char *, int);
#define add_assoc_string_ex(_arg, _key, _key_len, _str) _add_assoc_string_ex(_arg, _key, _key_len + 1, _str, 1)
extern int (*_add_assoc_stringl_ex)(zval *, const char *, uint, char *, uint, int);
#define add_assoc_stringl_ex(_arg, _key, _key_len, _str, _length) _add_assoc_stringl_ex(_arg, _key, _key_len + 1, _str, _length, 1)
#undef add_assoc_zval
#define add_assoc_zval(__arg, __key, __value) add_assoc_zval_ex(__arg, __key, strlen(__key), __value)
extern int (*_add_assoc_zval_ex)(zval *, const char *, uint, zval *);
#define add_assoc_zval_ex(_arg, _key, _key_len, _value) _add_assoc_zval_ex(_arg, _key, _key_len + 1, _value);
typedef long zend_long;
static zend_always_inline zend_long
zval_get_long(zval *op)
{
switch (Z_TYPE_P(op)) {
case IS_BOOL:
case IS_LONG:
return Z_LVAL_P(op);
case IS_DOUBLE:
return zend_dval_to_lval(Z_DVAL_P(op));
case IS_STRING:
{
double dval;
zend_long lval;
zend_uchar type = is_numeric_string(Z_STRVAL_P(op), Z_STRLEN_P(op), &lval, &dval, 0);
if (type == IS_LONG) {
return lval;
} else if (type == IS_DOUBLE) {
return zend_dval_to_lval(dval);
}
}
break;
EMPTY_SWITCH_DEFAULT_CASE()
}
return 0;
}
static zend_always_inline double
zval_get_double(zval *op)
{
switch (Z_TYPE_P(op)) {
case IS_BOOL:
case IS_LONG:
return (double)Z_LVAL_P(op);
case IS_DOUBLE:
return Z_DVAL_P(op);
case IS_STRING:
return zend_strtod(Z_STRVAL_P(op), NULL);
EMPTY_SWITCH_DEFAULT_CASE()
}
return 0.0;
}
static zend_always_inline zend_string *
zval_get_string(zval *op)
{
zend_string *zstr = ecalloc(1, sizeof(zend_string));
zstr->gc = 0;
ZSTR_VAL(zstr) = "";
ZSTR_LEN(zstr) = 0;
switch (Z_TYPE_P(op)) {
case IS_STRING:
ZSTR_VAL(zstr) = Z_STRVAL_P(op);
ZSTR_LEN(zstr) = Z_STRLEN_P(op);
break;
case IS_BOOL:
if (Z_LVAL_P(op)) {
ZSTR_VAL(zstr) = "1";
ZSTR_LEN(zstr) = 1;
}
break;
case IS_LONG: {
zstr->gc = 0x10;
ZSTR_LEN(zstr) = spprintf(&ZSTR_VAL(zstr), 0, "%ld", Z_LVAL_P(op));
break;
}
case IS_DOUBLE: {
zstr->gc = 0x10;
ZSTR_LEN(zstr) = spprintf(&ZSTR_VAL(zstr), 0, "%.16g", Z_DVAL_P(op));
break;
}
EMPTY_SWITCH_DEFAULT_CASE()
}
zstr->gc |= 0x01;
return zstr;
}
extern void (*_php_var_serialize)(smart_str *, zval **, php_serialize_data_t * TSRMLS_DC);
#define php_var_serialize(buf, struc, data) _php_var_serialize(buf, &struc, data TSRMLS_CC)
extern int (*_php_var_unserialize)(zval **, const unsigned char **, const unsigned char *, php_unserialize_data_t * TSRMLS_DC);
#define php_var_unserialize(rval, p, max, var_hash) _php_var_unserialize(&rval, p, max, var_hash TSRMLS_CC)
typedef int strlen_t;
#define PHPREDIS_ZVAL_IS_STRICT_FALSE(z) (Z_TYPE_P(z) == IS_BOOL && !Z_BVAL_P(z))
/* If ZEND_MOD_END isn't defined, use legacy version */
#ifndef ZEND_MOD_END
#define ZEND_MOD_END { NULL, NULL, NULL }
#endif
/* PHP_FE_END exists since 5.3.7 */
#ifndef PHP_FE_END
#define PHP_FE_END { NULL, NULL, NULL }
#endif
/* References don't need any actions */
#define ZVAL_DEREF(v) PHPREDIS_NOTUSED(v)
#else
#include
#include
typedef size_t strlen_t;
#define PHPREDIS_ZVAL_IS_STRICT_FALSE(z) (Z_TYPE_P(z) == IS_FALSE)
#endif
/* NULL check so Eclipse doesn't go crazy */
#ifndef NULL
#define NULL ((void *) 0)
#endif
#define REDIS_SOCK_STATUS_FAILED 0
#define REDIS_SOCK_STATUS_DISCONNECTED 1
#define REDIS_SOCK_STATUS_CONNECTED 2
#define _NL "\r\n"
/* properties */
#define REDIS_NOT_FOUND 0
#define REDIS_STRING 1
#define REDIS_SET 2
#define REDIS_LIST 3
#define REDIS_ZSET 4
#define REDIS_HASH 5
#ifdef PHP_WIN32
#define PHP_REDIS_API __declspec(dllexport)
#else
#define PHP_REDIS_API
#endif
/* reply types */
typedef enum _REDIS_REPLY_TYPE {
TYPE_EOF = -1,
TYPE_LINE = '+',
TYPE_INT = ':',
TYPE_ERR = '-',
TYPE_BULK = '$',
TYPE_MULTIBULK = '*'
} REDIS_REPLY_TYPE;
/* SCAN variants */
typedef enum _REDIS_SCAN_TYPE {
TYPE_SCAN,
TYPE_SSCAN,
TYPE_HSCAN,
TYPE_ZSCAN
} REDIS_SCAN_TYPE;
/* PUBSUB subcommands */
typedef enum _PUBSUB_TYPE {
PUBSUB_CHANNELS,
PUBSUB_NUMSUB,
PUBSUB_NUMPAT
} PUBSUB_TYPE;
/* options */
#define REDIS_OPT_SERIALIZER 1
#define REDIS_OPT_PREFIX 2
#define REDIS_OPT_READ_TIMEOUT 3
#define REDIS_OPT_SCAN 4
#define REDIS_OPT_FAILOVER 5
/* cluster options */
#define REDIS_FAILOVER_NONE 0
#define REDIS_FAILOVER_ERROR 1
#define REDIS_FAILOVER_DISTRIBUTE 2
#define REDIS_FAILOVER_DISTRIBUTE_SLAVES 3
/* serializers */
#define REDIS_SERIALIZER_NONE 0
#define REDIS_SERIALIZER_PHP 1
#define REDIS_SERIALIZER_IGBINARY 2
/* SCAN options */
#define REDIS_SCAN_NORETRY 0
#define REDIS_SCAN_RETRY 1
/* GETBIT/SETBIT offset range limits */
#define BITOP_MIN_OFFSET 0
#define BITOP_MAX_OFFSET 4294967295U
/* Transaction modes */
#define ATOMIC 0
#define MULTI 1
#define PIPELINE 2
#define IF_ATOMIC() if (redis_sock->mode == ATOMIC)
#define IF_NOT_ATOMIC() if (redis_sock->mode != ATOMIC)
#define IF_MULTI() if (redis_sock->mode & MULTI)
#define IF_NOT_MULTI() if (!(redis_sock->mode & MULTI))
#define IF_PIPELINE() if (redis_sock->mode & PIPELINE)
#define IF_NOT_PIPELINE() if (!(redis_sock->mode & PIPELINE))
#define PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len) do { \
if (redis_sock->pipeline_cmd == NULL) { \
redis_sock->pipeline_cmd = estrndup(cmd, cmd_len); \
} else { \
redis_sock->pipeline_cmd = erealloc(redis_sock->pipeline_cmd, \
redis_sock->pipeline_len + cmd_len); \
memcpy(&redis_sock->pipeline_cmd[redis_sock->pipeline_len], \
cmd, cmd_len); \
} \
redis_sock->pipeline_len += cmd_len; \
} while (0)
#define SOCKET_WRITE_COMMAND(redis_sock, cmd, cmd_len) \
if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) { \
efree(cmd); \
RETURN_FALSE; \
}
#define REDIS_SAVE_CALLBACK(callback, closure_context) do { \
fold_item *fi = malloc(sizeof(fold_item)); \
fi->fun = (void *)callback; \
fi->ctx = closure_context; \
fi->next = NULL; \
if (redis_sock->current) { \
redis_sock->current->next = fi; \
} \
redis_sock->current = fi; \
if (NULL == redis_sock->head) { \
redis_sock->head = redis_sock->current; \
} \
} while (0)
#define REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len) \
IF_PIPELINE() { \
PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len); \
} else { \
SOCKET_WRITE_COMMAND(redis_sock, cmd, cmd_len); \
} \
efree(cmd);
#define REDIS_PROCESS_RESPONSE_CLOSURE(function, closure_context) \
IF_NOT_PIPELINE() { \
if (redis_response_enqueued(redis_sock TSRMLS_CC) != SUCCESS) { \
RETURN_FALSE; \
} \
} \
REDIS_SAVE_CALLBACK(function, closure_context); \
RETURN_ZVAL(getThis(), 1, 0); \
#define REDIS_PROCESS_RESPONSE(function) else { \
REDIS_PROCESS_RESPONSE_CLOSURE(function, NULL) \
}
/* Clear redirection info */
#define REDIS_MOVED_CLEAR(redis_sock) \
redis_sock->redir_slot = 0; \
redis_sock->redir_port = 0; \
redis_sock->redir_type = MOVED_NONE; \
/* Process a command assuming our command where our command building
* function is redis__cmd */
#define REDIS_PROCESS_CMD(cmdname, resp_func) \
RedisSock *redis_sock; char *cmd; int cmd_len; void *ctx=NULL; \
if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL || \
redis_##cmdname##_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU,redis_sock, \
&cmd, &cmd_len, NULL, &ctx)==FAILURE) { \
RETURN_FALSE; \
} \
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); \
IF_ATOMIC() { \
resp_func(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, ctx); \
} else { \
REDIS_PROCESS_RESPONSE_CLOSURE(resp_func, ctx) \
}
/* Process a command but with a specific command building function
* and keyword which is passed to us*/
#define REDIS_PROCESS_KW_CMD(kw, cmdfunc, resp_func) \
RedisSock *redis_sock; char *cmd; int cmd_len; void *ctx=NULL; \
if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL || \
cmdfunc(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, kw, &cmd, \
&cmd_len, NULL, &ctx)==FAILURE) { \
RETURN_FALSE; \
} \
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len); \
IF_ATOMIC() { \
resp_func(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, ctx); \
} else { \
REDIS_PROCESS_RESPONSE_CLOSURE(resp_func, ctx) \
}
#define REDIS_STREAM_CLOSE_MARK_FAILED(redis_sock) \
redis_stream_close(redis_sock TSRMLS_CC); \
redis_sock->stream = NULL; \
redis_sock->mode = ATOMIC; \
redis_sock->status = REDIS_SOCK_STATUS_FAILED; \
redis_sock->watching = 0
/* Extended SET argument detection */
#define IS_EX_ARG(a) \
((a[0]=='e' || a[0]=='E') && (a[1]=='x' || a[1]=='X') && a[2]=='\0')
#define IS_PX_ARG(a) \
((a[0]=='p' || a[0]=='P') && (a[1]=='x' || a[1]=='X') && a[2]=='\0')
#define IS_NX_ARG(a) \
((a[0]=='n' || a[0]=='N') && (a[1]=='x' || a[1]=='X') && a[2]=='\0')
#define IS_XX_ARG(a) \
((a[0]=='x' || a[0]=='X') && (a[1]=='x' || a[1]=='X') && a[2]=='\0')
#define IS_EX_PX_ARG(a) (IS_EX_ARG(a) || IS_PX_ARG(a))
#define IS_NX_XX_ARG(a) (IS_NX_ARG(a) || IS_XX_ARG(a))
/* Given a string and length, validate a zRangeByLex argument. The semantics
* here are that the argument must start with '(' or '[' or be just the char
* '+' or '-' */
#define IS_LEX_ARG(s,l) \
(l>0 && (*s=='(' || *s=='[' || (l==1 && (*s=='+' || *s=='-'))))
#define REDIS_ENABLE_MODE(redis_sock, m) (redis_sock->mode |= m)
#define REDIS_DISABLE_MODE(redis_sock, m) (redis_sock->mode &= ~m)
typedef struct fold_item {
zval * (*fun)(INTERNAL_FUNCTION_PARAMETERS, void *, ...);
void *ctx;
struct fold_item *next;
} fold_item;
/* {{{ struct RedisSock */
typedef struct {
php_stream *stream;
zend_string *host;
short port;
zend_string *auth;
double timeout;
double read_timeout;
long retry_interval;
int failed;
int status;
int persistent;
int watching;
zend_string *persistent_id;
int serializer;
long dbNumber;
zend_string *prefix;
short mode;
fold_item *head;
fold_item *current;
char *pipeline_cmd;
size_t pipeline_len;
zend_string *err;
zend_bool lazy_connect;
int scan;
int readonly;
} RedisSock;
/* }}} */
#if (PHP_MAJOR_VERSION < 7)
typedef struct {
zend_object std;
RedisSock *sock;
} redis_object;
#else
typedef struct {
RedisSock *sock;
zend_object std;
} redis_object;
#endif
/** Argument info for any function expecting 0 args */
ZEND_BEGIN_ARG_INFO_EX(arginfo_void, 0, 0, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_key, 0, 0, 1)
ZEND_ARG_INFO(0, key)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_value, 0, 0, 1)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_key_value, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_key_expire_value, 0, 0, 3)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, expire)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_key_newkey, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, newkey)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_pairs, 0, 0, 1)
ZEND_ARG_ARRAY_INFO(0, pairs, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_nkeys, 0, 0, 1)
ZEND_ARG_INFO(0, key)
#if PHP_VERSION_ID >= 50600
ZEND_ARG_VARIADIC_INFO(0, other_keys)
#else
ZEND_ARG_INFO(0, ...)
#endif
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_dst_nkeys, 0, 0, 2)
ZEND_ARG_INFO(0, dst)
ZEND_ARG_INFO(0, key)
#if PHP_VERSION_ID >= 50600
ZEND_ARG_VARIADIC_INFO(0, other_keys)
#else
ZEND_ARG_INFO(0, ...)
#endif
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_key_min_max, 0, 0, 3)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, min)
ZEND_ARG_INFO(0, max)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_key_member, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, member)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_key_member_value, 0, 0, 3)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, member)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_key_members, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, member)
#if PHP_VERSION_ID >= 50600
ZEND_ARG_VARIADIC_INFO(0, other_members)
#else
ZEND_ARG_INFO(0, ...)
#endif
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_key_timestamp, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, timestamp)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_key_offset, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, offset)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_key_offset_value, 0, 0, 3)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, offset)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_key_start_end, 0, 0, 3)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, start)
ZEND_ARG_INFO(0, end)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_echo, 0, 0, 1)
ZEND_ARG_INFO(0, msg)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_expire, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, timeout)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_set, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, value)
ZEND_ARG_INFO(0, timeout)
ZEND_ARG_INFO(0, opt)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_lset, 0, 0, 3)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, index)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_blrpop, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, timeout_or_key)
// Can't have variadic keys before timeout.
#if PHP_VERSION_ID >= 50600
ZEND_ARG_VARIADIC_INFO(0, extra_args)
#else
ZEND_ARG_INFO(0, ...)
#endif
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_linsert, 0, 0, 4)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, position)
ZEND_ARG_INFO(0, pivot)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_lindex, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, index)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_brpoplpush, 0, 0, 3)
ZEND_ARG_INFO(0, src)
ZEND_ARG_INFO(0, dst)
ZEND_ARG_INFO(0, timeout)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_rpoplpush, 0, 0, 2)
ZEND_ARG_INFO(0, src)
ZEND_ARG_INFO(0, dst)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_sadd_array, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_ARRAY_INFO(0, options, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_srand_member, 0, 0, 1)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, count)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_zadd, 0, 0, 3)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, score)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_zincrby, 0, 0, 3)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, value)
ZEND_ARG_INFO(0, member)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_hmget, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_ARRAY_INFO(0, keys, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_hmset, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_ARRAY_INFO(0, pairs, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_bitop, 0, 0, 3)
ZEND_ARG_INFO(0, operation)
ZEND_ARG_INFO(0, ret_key)
ZEND_ARG_INFO(0, key)
#if PHP_VERSION_ID >= 50600
ZEND_ARG_VARIADIC_INFO(0, other_keys)
#else
ZEND_ARG_INFO(0, ...)
#endif
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_bitpos, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, bit)
ZEND_ARG_INFO(0, start)
ZEND_ARG_INFO(0, end)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_ltrim, 0, 0, 3)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, start)
ZEND_ARG_INFO(0, stop)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_publish, 0, 0, 2)
ZEND_ARG_INFO(0, channel)
ZEND_ARG_INFO(0, message)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_pfadd, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_ARRAY_INFO(0, elements, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_pfmerge, 0, 0, 2)
ZEND_ARG_INFO(0, dstkey)
ZEND_ARG_ARRAY_INFO(0, keys, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_restore, 0, 0, 3)
ZEND_ARG_INFO(0, ttl)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_smove, 0, 0, 3)
ZEND_ARG_INFO(0, src)
ZEND_ARG_INFO(0, dst)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_zrange, 0, 0, 3)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, start)
ZEND_ARG_INFO(0, end)
ZEND_ARG_INFO(0, scores)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_zrangebyscore, 0, 0, 3)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, start)
ZEND_ARG_INFO(0, end)
ZEND_ARG_ARRAY_INFO(0, options, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_zrangebylex, 0, 0, 3)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, min)
ZEND_ARG_INFO(0, max)
ZEND_ARG_INFO(0, offset)
ZEND_ARG_INFO(0, limit)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_zstore, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_ARRAY_INFO(0, keys, 0)
ZEND_ARG_ARRAY_INFO(0, weights, 1)
ZEND_ARG_INFO(0, aggregate)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_sort, 0, 0, 1)
ZEND_ARG_INFO(0, key)
ZEND_ARG_ARRAY_INFO(0, options, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_object, 0, 0, 2)
ZEND_ARG_INFO(0, field)
ZEND_ARG_INFO(0, key)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_subscribe, 0, 0, 1)
ZEND_ARG_ARRAY_INFO(0, channels, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_psubscribe, 0, 0, 1)
ZEND_ARG_ARRAY_INFO(0, patterns, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_unsubscribe, 0, 0, 1)
ZEND_ARG_INFO(0, channel)
#if PHP_VERSION_ID >= 50600
ZEND_ARG_VARIADIC_INFO(0, other_channels)
#else
ZEND_ARG_INFO(0, ...)
#endif
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_punsubscribe, 0, 0, 1)
ZEND_ARG_INFO(0, pattern)
#if PHP_VERSION_ID >= 50600
ZEND_ARG_VARIADIC_INFO(0, other_patterns)
#else
ZEND_ARG_INFO(0, ...)
#endif
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_eval, 0, 0, 1)
ZEND_ARG_INFO(0, script)
ZEND_ARG_INFO(0, args)
ZEND_ARG_INFO(0, num_keys)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_evalsha, 0, 0, 1)
ZEND_ARG_INFO(0, script_sha)
ZEND_ARG_INFO(0, args)
ZEND_ARG_INFO(0, num_keys)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_getoption, 0, 0, 1)
ZEND_ARG_INFO(0, option)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_setoption, 0, 0, 2)
ZEND_ARG_INFO(0, option)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_watch, 0, 0, 1)
ZEND_ARG_INFO(0, key)
#if PHP_VERSION_ID >= 50600
ZEND_ARG_VARIADIC_INFO(0, other_keys)
#else
ZEND_ARG_INFO(0, ...)
#endif
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_info, 0, 0, 0)
ZEND_ARG_INFO(0, option)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_command, 0, 0, 0)
#if PHP_VERSION_ID >= 50600
ZEND_ARG_VARIADIC_INFO(0, args)
#else
ZEND_ARG_INFO(0, ...)
#endif
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_rawcommand, 0, 0, 1)
ZEND_ARG_INFO(0, cmd)
#if PHP_VERSION_ID >= 50600
ZEND_ARG_VARIADIC_INFO(0, args)
#else
ZEND_ARG_INFO(0, ...)
#endif
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_client, 0, 0, 1)
ZEND_ARG_INFO(0, cmd)
#if PHP_VERSION_ID >= 50600
ZEND_ARG_VARIADIC_INFO(0, args)
#else
ZEND_ARG_INFO(0, ...)
#endif
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_config, 0, 0, 2)
ZEND_ARG_INFO(0, cmd)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_pubsub, 0, 0, 1)
ZEND_ARG_INFO(0, cmd)
#if PHP_VERSION_ID >= 50600
ZEND_ARG_VARIADIC_INFO(0, args)
#else
ZEND_ARG_INFO(0, ...)
#endif
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_script, 0, 0, 1)
ZEND_ARG_INFO(0, cmd)
#if PHP_VERSION_ID >= 50600
ZEND_ARG_VARIADIC_INFO(0, args)
#else
ZEND_ARG_INFO(0, ...)
#endif
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_slowlog, 0, 0, 1)
ZEND_ARG_INFO(0, arg)
ZEND_ARG_INFO(0, option)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_geoadd, 0, 0, 4)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, lng)
ZEND_ARG_INFO(0, lat)
ZEND_ARG_INFO(0, member)
#if PHP_VERSION_ID >= 50600
ZEND_ARG_VARIADIC_INFO(0, other_triples)
#else
ZEND_ARG_INFO(0, ...)
#endif
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_geodist, 0, 0, 3)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, src)
ZEND_ARG_INFO(0, dst)
ZEND_ARG_INFO(0, unit)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_georadius, 0, 0, 5)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, lng)
ZEND_ARG_INFO(0, lan)
ZEND_ARG_INFO(0, radius)
ZEND_ARG_INFO(0, unit)
ZEND_ARG_ARRAY_INFO(0, opts, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_georadiusbymember, 0, 0, 4)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, member)
ZEND_ARG_INFO(0, radius)
ZEND_ARG_INFO(0, unit)
ZEND_ARG_ARRAY_INFO(0, opts, 0)
ZEND_END_ARG_INFO()
#endif
redis-3.1.6/library.c 0000644 0001750 0000012 00000174667 13223116600 015235 0 ustar pyatsukhnenko wheel #ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "common.h"
#include "php_network.h"
#include
#ifndef _MSC_VER
#include /* TCP_NODELAY */
#include
#endif
#ifdef HAVE_REDIS_IGBINARY
#include "igbinary/igbinary.h"
#endif
#include
#include "php_redis.h"
#include "library.h"
#include "redis_commands.h"
#include
#define UNSERIALIZE_NONE 0
#define UNSERIALIZE_KEYS 1
#define UNSERIALIZE_VALS 2
#define UNSERIALIZE_ALL 3
#define SCORE_DECODE_NONE 0
#define SCORE_DECODE_INT 1
#define SCORE_DECODE_DOUBLE 2
#ifdef PHP_WIN32
# if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION <= 4
/* This proto is available from 5.5 on only */
PHP_REDIS_API int usleep(unsigned int useconds);
# endif
#endif
#if (PHP_MAJOR_VERSION < 7)
int (*_add_next_index_string)(zval *, const char *, int) = &add_next_index_string;
int (*_add_next_index_stringl)(zval *, const char *, uint, int) = &add_next_index_stringl;
int (*_add_assoc_bool_ex)(zval *, const char *, uint, int) = &add_assoc_bool_ex;
int (*_add_assoc_long_ex)(zval *, const char *, uint, long) = &add_assoc_long_ex;
int (*_add_assoc_double_ex)(zval *, const char *, uint, double) = &add_assoc_double_ex;
int (*_add_assoc_string_ex)(zval *, const char *, uint, char *, int) = &add_assoc_string_ex;
int (*_add_assoc_stringl_ex)(zval *, const char *, uint, char *, uint, int) = &add_assoc_stringl_ex;
int (*_add_assoc_zval_ex)(zval *, const char *, uint, zval *) = &add_assoc_zval_ex;
void (*_php_var_serialize)(smart_str *, zval **, php_serialize_data_t * TSRMLS_DC) = &php_var_serialize;
int (*_php_var_unserialize)(zval **, const unsigned char **, const unsigned char *, php_unserialize_data_t * TSRMLS_DC) = &php_var_unserialize;
#endif
extern zend_class_entry *redis_ce;
extern zend_class_entry *redis_exception_ce;
/* Helper to reselect the proper DB number when we reconnect */
static int reselect_db(RedisSock *redis_sock TSRMLS_DC) {
char *cmd, *response;
int cmd_len, response_len;
cmd_len = redis_spprintf(redis_sock, NULL TSRMLS_CC, &cmd, "SELECT", "d",
redis_sock->dbNumber);
if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) {
efree(cmd);
return -1;
}
efree(cmd);
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) {
return -1;
}
if (strncmp(response, "+OK", 3)) {
efree(response);
return -1;
}
efree(response);
return 0;
}
/* Helper to resend AUTH in the case of a reconnect */
static int resend_auth(RedisSock *redis_sock TSRMLS_DC) {
char *cmd, *response;
int cmd_len, response_len;
cmd_len = redis_spprintf(redis_sock, NULL TSRMLS_CC, &cmd, "AUTH", "s",
ZSTR_VAL(redis_sock->auth), ZSTR_LEN(redis_sock->auth));
if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) {
efree(cmd);
return -1;
}
efree(cmd);
response = redis_sock_read(redis_sock, &response_len TSRMLS_CC);
if (response == NULL) {
return -1;
}
if (strncmp(response, "+OK", 3)) {
efree(response);
return -1;
}
efree(response);
return 0;
}
/* Helper function that will throw an exception for a small number of ERR codes
* returned by Redis. Typically we just return FALSE to the caller in the event
* of an ERROR reply, but for the following error types:
* 1) MASTERDOWN
* 2) AUTH
* 3) LOADING
*/
static void
redis_error_throw(RedisSock *redis_sock TSRMLS_DC)
{
if (redis_sock != NULL && redis_sock->err != NULL &&
memcmp(ZSTR_VAL(redis_sock->err), "ERR", sizeof("ERR") - 1) != 0 &&
memcmp(ZSTR_VAL(redis_sock->err), "NOSCRIPT", sizeof("NOSCRIPT") - 1) != 0 &&
memcmp(ZSTR_VAL(redis_sock->err), "WRONGTYPE", sizeof("WRONGTYPE") - 1) != 0
) {
zend_throw_exception(redis_exception_ce, ZSTR_VAL(redis_sock->err), 0 TSRMLS_CC);
}
}
PHP_REDIS_API void redis_stream_close(RedisSock *redis_sock TSRMLS_DC) {
if (!redis_sock->persistent) {
php_stream_close(redis_sock->stream);
} else {
php_stream_pclose(redis_sock->stream);
}
}
PHP_REDIS_API int
redis_check_eof(RedisSock *redis_sock, int no_throw TSRMLS_DC)
{
int count;
if (!redis_sock->stream) {
return -1;
}
/* NOITCE: set errno = 0 here
*
* There is a bug in php socket stream to check liveness of a connection:
* if (0 >= recv(sock->socket, &buf, sizeof(buf), MSG_PEEK) && php_socket_errno() != EWOULDBLOCK) {
* alive = 0;
* }
* If last errno is EWOULDBLOCK and recv returns 0 because of connection closed, alive would not be
* set to 0. However, the connection is close indeed. The php_stream_eof is not reliable. This will
* cause a "read error on connection" exception when use a closed persistent connection.
*
* We work around this by set errno = 0 first.
*
* Bug fix of php: https://github.com/php/php-src/pull/1456
* */
errno = 0;
if (php_stream_eof(redis_sock->stream) == 0) {
/* Success */
return 0;
} else if (redis_sock->mode == MULTI || redis_sock->watching) {
REDIS_STREAM_CLOSE_MARK_FAILED(redis_sock);
if (!no_throw) {
zend_throw_exception(redis_exception_ce,
"Connection lost and socket is in MULTI/watching mode",
0 TSRMLS_CC);
}
return -1;
}
/* TODO: configurable max retry count */
for (count = 0; count < 10; ++count) {
/* close existing stream before reconnecting */
if (redis_sock->stream) {
redis_stream_close(redis_sock TSRMLS_CC);
redis_sock->stream = NULL;
}
// Wait for a while before trying to reconnect
if (redis_sock->retry_interval) {
// Random factor to avoid having several (or many) concurrent connections trying to reconnect at the same time
long retry_interval = (count ? redis_sock->retry_interval : (php_rand(TSRMLS_C) % redis_sock->retry_interval));
usleep(retry_interval);
}
/* reconnect */
if (redis_sock_connect(redis_sock TSRMLS_CC) == 0) {
/* check for EOF again. */
errno = 0;
if (php_stream_eof(redis_sock->stream) == 0) {
/* If we're using a password, attempt a reauthorization */
if (redis_sock->auth && resend_auth(redis_sock TSRMLS_CC) != 0) {
break;
}
/* If we're using a non-zero db, reselect it */
if (redis_sock->dbNumber && reselect_db(redis_sock TSRMLS_CC) != 0) {
break;
}
/* Success */
return 0;
}
}
}
/* close stream if still here */
if (redis_sock->stream) {
REDIS_STREAM_CLOSE_MARK_FAILED(redis_sock);
}
if (!no_throw) {
zend_throw_exception(redis_exception_ce, "Connection lost", 0 TSRMLS_CC);
}
return -1;
}
PHP_REDIS_API int
redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
REDIS_SCAN_TYPE type, zend_long *iter)
{
REDIS_REPLY_TYPE reply_type;
long reply_info;
char *p_iter;
/* Our response should have two multibulk replies */
if(redis_read_reply_type(redis_sock, &reply_type, &reply_info TSRMLS_CC)<0
|| reply_type != TYPE_MULTIBULK || reply_info != 2)
{
return -1;
}
/* The BULK response iterator */
if(redis_read_reply_type(redis_sock, &reply_type, &reply_info TSRMLS_CC)<0
|| reply_type != TYPE_BULK)
{
return -1;
}
/* Attempt to read the iterator */
if(!(p_iter = redis_sock_read_bulk_reply(redis_sock, reply_info TSRMLS_CC))) {
return -1;
}
/* Push the iterator out to the caller */
*iter = atol(p_iter);
efree(p_iter);
/* Read our actual keys/members/etc differently depending on what kind of
scan command this is. They all come back in slightly different ways */
switch(type) {
case TYPE_SCAN:
return redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU,
redis_sock, NULL, NULL);
case TYPE_SSCAN:
return redis_sock_read_multibulk_reply(
INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
case TYPE_ZSCAN:
return redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAM_PASSTHRU,
redis_sock, NULL, NULL);
case TYPE_HSCAN:
return redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAM_PASSTHRU,
redis_sock, NULL, NULL);
default:
return -1;
}
}
PHP_REDIS_API int redis_subscribe_response(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, zval *z_tab,
void *ctx)
{
subscribeContext *sctx = (subscribeContext*)ctx;
zval *z_tmp, z_resp;
// Consume response(s) from subscribe, which will vary on argc
while(sctx->argc--) {
if (!redis_sock_read_multibulk_reply_zval(
INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_resp)
) {
efree(sctx);
return -1;
}
// We'll need to find the command response
if ((z_tmp = zend_hash_index_find(Z_ARRVAL(z_resp), 0)) == NULL) {
zval_dtor(&z_resp);
efree(sctx);
return -1;
}
// Make sure the command response matches the command we called
if(strcasecmp(Z_STRVAL_P(z_tmp), sctx->kw) !=0) {
zval_dtor(&z_resp);
efree(sctx);
return -1;
}
zval_dtor(&z_resp);
}
#if (PHP_MAJOR_VERSION < 7)
zval *z_ret, **z_args[4];
sctx->cb.retval_ptr_ptr = &z_ret;
#else
zval z_ret, z_args[4];
sctx->cb.retval = &z_ret;
#endif
sctx->cb.params = z_args;
sctx->cb.no_separation = 0;
/* Multibulk response, {[pattern], type, channel, payload } */
while(1) {
zval *z_type, *z_chan, *z_pat = NULL, *z_data;
HashTable *ht_tab;
int tab_idx=1, is_pmsg;
if (!redis_sock_read_multibulk_reply_zval(
INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_resp)) break;
ht_tab = Z_ARRVAL(z_resp);
if ((z_type = zend_hash_index_find(ht_tab, 0)) == NULL ||
Z_TYPE_P(z_type) != IS_STRING
) {
break;
}
// Check for message or pmessage
if(!strncmp(Z_STRVAL_P(z_type), "message", 7) ||
!strncmp(Z_STRVAL_P(z_type), "pmessage", 8))
{
is_pmsg = *Z_STRVAL_P(z_type)=='p';
} else {
break;
}
// Extract pattern if it's a pmessage
if(is_pmsg) {
if ((z_pat = zend_hash_index_find(ht_tab, tab_idx++)) == NULL) {
break;
}
}
// Extract channel and data
if ((z_chan = zend_hash_index_find(ht_tab, tab_idx++)) == NULL ||
(z_data = zend_hash_index_find(ht_tab, tab_idx++)) == NULL
) {
break;
}
// Different args for SUBSCRIBE and PSUBSCRIBE
#if (PHP_MAJOR_VERSION < 7)
z_args[0] = &getThis();
if(is_pmsg) {
z_args[1] = &z_pat;
z_args[2] = &z_chan;
z_args[3] = &z_data;
} else {
z_args[1] = &z_chan;
z_args[2] = &z_data;
}
#else
z_args[0] = *getThis();
if(is_pmsg) {
z_args[1] = *z_pat;
z_args[2] = *z_chan;
z_args[3] = *z_data;
} else {
z_args[1] = *z_chan;
z_args[2] = *z_data;
}
#endif
// Set arg count
sctx->cb.param_count = tab_idx;
// Execute callback
if(zend_call_function(&(sctx->cb), &(sctx->cb_cache) TSRMLS_CC)
==FAILURE)
{
break;
}
// If we have a return value free it
zval_ptr_dtor(&z_ret);
zval_dtor(&z_resp);
}
// This is an error state, clean up
zval_dtor(&z_resp);
efree(sctx);
return -1;
}
PHP_REDIS_API int redis_unsubscribe_response(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, zval *z_tab,
void *ctx)
{
subscribeContext *sctx = (subscribeContext*)ctx;
zval *z_chan, zv, *z_ret = &zv, z_resp;
int i;
array_init(z_ret);
for (i = 0; i < sctx->argc; i++) {
if (!redis_sock_read_multibulk_reply_zval(
INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_resp) ||
(z_chan = zend_hash_index_find(Z_ARRVAL(z_resp), 1)) == NULL
) {
zval_dtor(z_ret);
return -1;
}
add_assoc_bool(z_ret, Z_STRVAL_P(z_chan), 1);
zval_dtor(&z_resp);
}
efree(sctx);
RETVAL_ZVAL(z_ret, 0, 1);
// Success
return 0;
}
PHP_REDIS_API zval *
redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, zval *z_tab)
{
char inbuf[4096];
int numElems;
size_t len;
ZVAL_NULL(z_tab);
if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len TSRMLS_CC) < 0) {
return NULL;
}
if(inbuf[0] != '*') {
return NULL;
}
numElems = atoi(inbuf+1);
array_init(z_tab);
redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab,
numElems, UNSERIALIZE_ALL);
return z_tab;
}
/**
* redis_sock_read_bulk_reply
*/
PHP_REDIS_API char *
redis_sock_read_bulk_reply(RedisSock *redis_sock, int bytes TSRMLS_DC)
{
int offset = 0;
char *reply, c[2];
size_t got;
if (-1 == bytes || -1 == redis_check_eof(redis_sock, 0 TSRMLS_CC)) {
return NULL;
}
/* Allocate memory for string */
reply = emalloc(bytes+1);
/* Consume bulk string */
while(offset < bytes) {
got = php_stream_read(redis_sock->stream, reply + offset, bytes-offset);
if (got == 0) break;
offset += got;
}
/* Protect against reading too few bytes */
if (offset < bytes) {
/* Error or EOF */
zend_throw_exception(redis_exception_ce,
"socket error on read socket", 0 TSRMLS_CC);
efree(reply);
return NULL;
}
/* Consume \r\n and null terminate reply string */
php_stream_read(redis_sock->stream, c, 2);
reply[bytes] = '\0';
return reply;
}
/**
* redis_sock_read
*/
PHP_REDIS_API char *
redis_sock_read(RedisSock *redis_sock, int *buf_len TSRMLS_DC)
{
char inbuf[4096];
size_t len;
*buf_len = 0;
if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len TSRMLS_CC) < 0) {
return NULL;
}
switch(inbuf[0]) {
case '-':
redis_sock_set_err(redis_sock, inbuf+1, len);
/* Filter our ERROR through the few that should actually throw */
redis_error_throw(redis_sock TSRMLS_CC);
return NULL;
case '$':
*buf_len = atoi(inbuf + 1);
return redis_sock_read_bulk_reply(redis_sock, *buf_len TSRMLS_CC);
case '*':
/* For null multi-bulk replies (like timeouts from brpoplpush): */
if(memcmp(inbuf + 1, "-1", 2) == 0) {
return NULL;
}
/* fall through */
case '+':
case ':':
/* Single Line Reply */
/* +OK or :123 */
if (len > 1) {
*buf_len = len;
return estrndup(inbuf, *buf_len);
}
default:
zend_throw_exception_ex(
redis_exception_ce,
0 TSRMLS_CC,
"protocol error, got '%c' as reply type byte\n",
inbuf[0]
);
}
return NULL;
}
/* A simple union to store the various arg types we might handle in our
* redis_spprintf command formatting function */
union resparg {
char *str;
zend_string *zstr;
zval *zv;
int ival;
long lval;
double dval;
};
/* A printf like method to construct a Redis RESP command. It has been extended
* to take a few different format specifiers that are convienient to phpredis.
*
* s - C string followed by length as a strlen_t
* S - Pointer to a zend_string
* k - Same as 's' but the value will be prefixed if phpredis is set up do do
* that and the working slot will be set if it has been passed.
* v - A z_val which will be serialized if phpredis is configured to serialize.
* f - A double value
* F - Alias to 'f'
* i - An integer
* d - Alias to 'i'
* l - A long
* L - Alias to 'l'
*/
PHP_REDIS_API int
redis_spprintf(RedisSock *redis_sock, short *slot TSRMLS_DC, char **ret, char *kw, char *fmt, ...) {
smart_string cmd = {0};
va_list ap;
union resparg arg;
char *dup;
int argfree;
strlen_t arglen;
va_start(ap, fmt);
/* Header */
redis_cmd_init_sstr(&cmd, strlen(fmt), kw, strlen(kw));
while (*fmt) {
switch (*fmt) {
case 's':
arg.str = va_arg(ap, char*);
arglen = va_arg(ap, strlen_t);
redis_cmd_append_sstr(&cmd, arg.str, arglen);
break;
case 'S':
arg.zstr = va_arg(ap, zend_string*);
redis_cmd_append_sstr(&cmd, ZSTR_VAL(arg.zstr), ZSTR_LEN(arg.zstr));
break;
case 'k':
arg.str = va_arg(ap, char*);
arglen = va_arg(ap, strlen_t);
argfree = redis_key_prefix(redis_sock, &arg.str, &arglen);
redis_cmd_append_sstr(&cmd, arg.str, arglen);
if (slot) *slot = cluster_hash_key(arg.str, arglen);
if (argfree) efree(arg.str);
break;
case 'v':
arg.zv = va_arg(ap, zval*);
argfree = redis_serialize(redis_sock, arg.zv, &dup, &arglen TSRMLS_CC);
redis_cmd_append_sstr(&cmd, dup, arglen);
if (argfree) efree(dup);
break;
case 'f':
case 'F':
arg.dval = va_arg(ap, double);
redis_cmd_append_sstr_dbl(&cmd, arg.dval);
break;
case 'i':
case 'd':
arg.ival = va_arg(ap, int);
redis_cmd_append_sstr_int(&cmd, arg.ival);
break;
case 'l':
case 'L':
arg.lval = va_arg(ap, long);
redis_cmd_append_sstr_long(&cmd, arg.lval);
break;
}
fmt++;
}
/* varargs cleanup */
va_end(ap);
/* Null terminate */
smart_string_0(&cmd);
/* Push command string, return length */
*ret = cmd.c;
return cmd.len;
}
/*
* Given a smart string, number of arguments, a keyword, and the length of the keyword
* initialize our smart string with the proper Redis header for the command to follow
*/
int redis_cmd_init_sstr(smart_string *str, int num_args, char *keyword, int keyword_len) {
smart_string_appendc(str, '*');
smart_string_append_long(str, num_args + 1);
smart_string_appendl(str, _NL, sizeof(_NL) -1);
smart_string_appendc(str, '$');
smart_string_append_long(str, keyword_len);
smart_string_appendl(str, _NL, sizeof(_NL) - 1);
smart_string_appendl(str, keyword, keyword_len);
smart_string_appendl(str, _NL, sizeof(_NL) - 1);
return str->len;
}
/*
* Append a command sequence to a smart_string
*/
int redis_cmd_append_sstr(smart_string *str, char *append, int append_len) {
smart_string_appendc(str, '$');
smart_string_append_long(str, append_len);
smart_string_appendl(str, _NL, sizeof(_NL) - 1);
smart_string_appendl(str, append, append_len);
smart_string_appendl(str, _NL, sizeof(_NL) - 1);
/* Return our new length */
return str->len;
}
/*
* Append an integer to a smart string command
*/
int redis_cmd_append_sstr_int(smart_string *str, int append) {
char int_buf[32];
int int_len = snprintf(int_buf, sizeof(int_buf), "%d", append);
return redis_cmd_append_sstr(str, int_buf, int_len);
}
/*
* Append a long to a smart string command
*/
int redis_cmd_append_sstr_long(smart_string *str, long append) {
char long_buf[32];
int long_len = snprintf(long_buf, sizeof(long_buf), "%ld", append);
return redis_cmd_append_sstr(str, long_buf, long_len);
}
/*
* Append a double to a smart string command
*/
int
redis_cmd_append_sstr_dbl(smart_string *str, double value)
{
char tmp[64];
int len, retval;
/* Convert to string */
len = snprintf(tmp, sizeof(tmp), "%.16g", value);
// Append the string
retval = redis_cmd_append_sstr(str, tmp, len);
/* Return new length */
return retval;
}
/* Append a zval to a redis command. The value will be serialized if we are
* configured to do that */
int redis_cmd_append_sstr_zval(smart_string *str, zval *z, RedisSock *redis_sock TSRMLS_DC) {
char *val;
strlen_t vallen;
int valfree, retval;
valfree = redis_serialize(redis_sock, z, &val, &vallen TSRMLS_CC);
retval = redis_cmd_append_sstr(str, val, vallen);
if (valfree) efree(val);
return retval;
}
/* Append a string key to a redis command. This function takes care of prefixing the key
* for the caller and setting the slot argument if it is passed non null */
int redis_cmd_append_sstr_key(smart_string *str, char *key, strlen_t len, RedisSock *redis_sock, short *slot) {
int valfree, retval;
valfree = redis_key_prefix(redis_sock, &key, &len);
if (slot) *slot = cluster_hash_key(key, len);
retval = redis_cmd_append_sstr(str, key, len);
if (valfree) efree(key);
return retval;
}
PHP_REDIS_API void redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
char *response;
int response_len;
double ret;
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) {
IF_NOT_ATOMIC() {
add_next_index_bool(z_tab, 0);
return;
} else {
RETURN_FALSE;
}
}
ret = atof(response);
efree(response);
IF_NOT_ATOMIC() {
add_next_index_double(z_tab, ret);
} else {
RETURN_DOUBLE(ret);
}
}
PHP_REDIS_API void redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
char *response;
int response_len;
long l;
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) {
IF_NOT_ATOMIC() {
add_next_index_bool(z_tab, 0);
return;
} else {
RETURN_FALSE;
}
}
if (strncmp(response, "+string", 7) == 0) {
l = REDIS_STRING;
} else if (strncmp(response, "+set", 4) == 0){
l = REDIS_SET;
} else if (strncmp(response, "+list", 5) == 0){
l = REDIS_LIST;
} else if (strncmp(response, "+zset", 5) == 0){
l = REDIS_ZSET;
} else if (strncmp(response, "+hash", 5) == 0){
l = REDIS_HASH;
} else {
l = REDIS_NOT_FOUND;
}
efree(response);
IF_NOT_ATOMIC() {
add_next_index_long(z_tab, l);
} else {
RETURN_LONG(l);
}
}
PHP_REDIS_API void redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
char *response;
int response_len;
zval zv = {{0}}, *z_ret = &zv;
/* Read bulk response */
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
/* Parse it into a zval array */
redis_parse_info_response(response, z_ret);
/* Free source response */
efree(response);
IF_NOT_ATOMIC() {
add_next_index_zval(z_tab, z_ret);
} else {
RETVAL_ZVAL(z_ret, 0, 1);
}
}
PHP_REDIS_API void
redis_parse_info_response(char *response, zval *z_ret)
{
char *cur, *pos;
array_init(z_ret);
cur = response;
while(1) {
/* skip comments and empty lines */
if (*cur == '#' || *cur == '\r') {
if ((cur = strstr(cur, _NL)) == NULL) {
break;
}
cur += 2;
continue;
}
/* key */
if ((pos = strchr(cur, ':')) == NULL) {
break;
}
char *key = cur;
int key_len = pos - cur;
key[key_len] = '\0';
/* value */
cur = pos + 1;
if ((pos = strstr(cur, _NL)) == NULL) {
break;
}
char *value = cur;
int value_len = pos - cur;
value[value_len] = '\0';
double dval;
zend_long lval;
zend_uchar type = is_numeric_string(value, value_len, &lval, &dval, 0);
if (type == IS_LONG) {
add_assoc_long_ex(z_ret, key, key_len, lval);
} else if (type == IS_DOUBLE) {
add_assoc_double_ex(z_ret, key, key_len, dval);
} else {
add_assoc_stringl_ex(z_ret, key, key_len, value, value_len);
}
cur = pos + 2; /* \r, \n */
}
}
/*
* Specialized handling of the CLIENT LIST output so it comes out in a simple way for PHP userland code
* to handle.
*/
PHP_REDIS_API void redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab) {
char *resp;
int resp_len;
/* Make sure we can read the bulk response from Redis */
if ((resp = redis_sock_read(redis_sock, &resp_len TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
zval zv, *z_ret = &zv;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_ret);
#endif
/* Parse it out */
redis_parse_client_list_response(resp, z_ret);
/* Free our response */
efree(resp);
/* Return or append depending if we're atomic */
IF_NOT_ATOMIC() {
add_next_index_zval(z_tab, z_ret);
} else {
RETVAL_ZVAL(z_ret, 0, 1);
}
}
PHP_REDIS_API void
redis_parse_client_list_response(char *response, zval *z_ret)
{
char *p, *lpos, *kpos = NULL, *vpos = NULL, *p2, *key, *value;
int klen = 0, done = 0, is_numeric;
// Allocate memory for our response
array_init(z_ret);
/* Allocate memory for one user (there should be at least one, namely us!) */
zval zv, *z_sub_result = &zv;
#if (PHP_MAJOR_VERSION < 7)
ALLOC_INIT_ZVAL(z_sub_result);
#endif
array_init(z_sub_result);
// Pointers for parsing
p = response;
lpos = response;
/* While we've got more to parse */
while(!done) {
/* What character are we on */
switch(*p) {
/* We're done */
case '\0':
done = 1;
break;
/* \n, ' ' mean we can pull a k/v pair */
case '\n':
case ' ':
/* Grab our value */
vpos = lpos;
/* There is some communication error or Redis bug if we don't
have a key and value, but check anyway. */
if(kpos && vpos) {
/* Allocate, copy in our key */
key = estrndup(kpos, klen);
/* Allocate, copy in our value */
value = estrndup(lpos, p - lpos);
/* Treat numbers as numbers, strings as strings */
is_numeric = 1;
for(p2 = value; *p2; ++p2) {
if(*p2 < '0' || *p2 > '9') {
is_numeric = 0;
break;
}
}
/* Add as a long or string, depending */
if(is_numeric == 1) {
add_assoc_long(z_sub_result, key, atol(value));
} else {
add_assoc_string(z_sub_result, key, value);
}
efree(value);
// If we hit a '\n', then we can add this user to our list
if(*p == '\n') {
/* Add our user */
add_next_index_zval(z_ret, z_sub_result);
/* If we have another user, make another one */
if(*(p+1) != '\0') {
#if (PHP_MAJOR_VERSION < 7)
ALLOC_INIT_ZVAL(z_sub_result);
#endif
array_init(z_sub_result);
}
}
// Free our key
efree(key);
} else {
// Something is wrong
zval_dtor(z_ret);
ZVAL_BOOL(z_ret, 0);
return;
}
/* Move forward */
lpos = p + 1;
break;
/* We can pull the key and null terminate at our sep */
case '=':
/* Key, key length */
kpos = lpos;
klen = p - lpos;
/* Move forward */
lpos = p + 1;
break;
}
/* Increment */
p++;
}
}
PHP_REDIS_API void
redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
zval *z_tab, void *ctx,
SuccessCallback success_callback)
{
char *response;
int response_len;
zend_bool ret = 0;
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) != NULL) {
ret = (*response == '+');
efree(response);
}
if (ret && success_callback != NULL) {
success_callback(redis_sock);
}
IF_NOT_ATOMIC() {
add_next_index_bool(z_tab, ret);
} else {
RETURN_BOOL(ret);
}
}
PHP_REDIS_API void redis_boolean_response(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, zval *z_tab,
void *ctx)
{
redis_boolean_response_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
z_tab, ctx, NULL);
}
PHP_REDIS_API void redis_long_response(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, zval * z_tab,
void *ctx)
{
char *response;
int response_len;
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC))
== NULL)
{
IF_NOT_ATOMIC() {
add_next_index_bool(z_tab, 0);
return;
} else {
RETURN_FALSE;
}
}
if(response[0] == ':') {
#ifdef PHP_WIN32
__int64 ret = _atoi64(response + 1);
#else
long long ret = atoll(response + 1);
#endif
IF_NOT_ATOMIC() {
if(ret > LONG_MAX) { /* overflow */
add_next_index_stringl(z_tab, response + 1, response_len - 1);
} else {
add_next_index_long(z_tab, (long)ret);
}
} else {
if(ret > LONG_MAX) { /* overflow */
RETVAL_STRINGL(response + 1, response_len - 1);
} else {
RETVAL_LONG((long)ret);
}
}
} else {
IF_NOT_ATOMIC() {
add_next_index_null(z_tab);
} else {
RETVAL_FALSE;
}
}
efree(response);
}
/* Helper method to convert [key, value, key, value] into [key => value,
* key => value] when returning data to the caller. Depending on our decode
* flag we'll convert the value data types */
static void array_zip_values_and_scores(RedisSock *redis_sock, zval *z_tab,
int decode TSRMLS_DC)
{
zval zv, *z_ret = &zv;
HashTable *keytable;
array_init(z_ret);
keytable = Z_ARRVAL_P(z_tab);
for(zend_hash_internal_pointer_reset(keytable);
zend_hash_has_more_elements(keytable) == SUCCESS;
zend_hash_move_forward(keytable)) {
zval *z_key_p, *z_value_p;
if ((z_key_p = zend_hash_get_current_data(keytable)) == NULL) {
continue; /* this should never happen, according to the PHP people. */
}
/* get current value, a key */
zend_string *hkey = zval_get_string(z_key_p);
/* move forward */
zend_hash_move_forward(keytable);
/* fetch again */
if ((z_value_p = zend_hash_get_current_data(keytable)) == NULL) {
zend_string_release(hkey);
continue; /* this should never happen, according to the PHP people. */
}
/* get current value, a hash value now. */
char *hval = Z_STRVAL_P(z_value_p);
/* Decode the score depending on flag */
if (decode == SCORE_DECODE_INT && Z_STRLEN_P(z_value_p) > 0) {
add_assoc_long_ex(z_ret, ZSTR_VAL(hkey), ZSTR_LEN(hkey), atoi(hval+1));
} else if (decode == SCORE_DECODE_DOUBLE) {
add_assoc_double_ex(z_ret, ZSTR_VAL(hkey), ZSTR_LEN(hkey), atof(hval));
} else {
zval zv0, *z = &zv0;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z);
#endif
ZVAL_ZVAL(z, z_value_p, 1, 0);
add_assoc_zval_ex(z_ret, ZSTR_VAL(hkey), ZSTR_LEN(hkey), z);
}
zend_string_release(hkey);
}
/* replace */
zval_dtor(z_tab);
ZVAL_ZVAL(z_tab, z_ret, 1, 0);
zval_dtor(z_ret);
}
static int
redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
zval *z_tab, int unserialize, int decode)
{
char inbuf[4096];
int numElems;
size_t len;
if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len TSRMLS_CC) < 0) {
return -1;
}
if(inbuf[0] != '*') {
IF_NOT_ATOMIC() {
add_next_index_bool(z_tab, 0);
} else {
RETVAL_FALSE;
}
return -1;
}
numElems = atoi(inbuf+1);
zval zv, *z_multi_result = &zv;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_multi_result);
#endif
array_init(z_multi_result); /* pre-allocate array for multi's results. */
/* Grab our key, value, key, value array */
redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
z_multi_result, numElems, unserialize);
/* Zip keys and values */
array_zip_values_and_scores(redis_sock, z_multi_result, decode TSRMLS_CC);
IF_NOT_ATOMIC() {
add_next_index_zval(z_tab, z_multi_result);
} else {
RETVAL_ZVAL(z_multi_result, 0, 1);
}
return 0;
}
/* Zipped key => value reply but we don't touch anything (e.g. CONFIG GET) */
PHP_REDIS_API int redis_mbulk_reply_zipped_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
{
return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
z_tab, UNSERIALIZE_NONE, SCORE_DECODE_NONE);
}
/* Zipped key => value reply unserializing keys and decoding the score as an integer (PUBSUB) */
PHP_REDIS_API int redis_mbulk_reply_zipped_keys_int(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
zval *z_tab, void *ctx)
{
return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
z_tab, UNSERIALIZE_KEYS, SCORE_DECODE_INT);
}
/* Zipped key => value reply unserializing keys and decoding the score as a double (ZSET commands) */
PHP_REDIS_API int redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
zval *z_tab, void *ctx)
{
return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
z_tab, UNSERIALIZE_KEYS, SCORE_DECODE_DOUBLE);
}
/* Zipped key => value reply where only the values are unserialized (e.g. HMGET) */
PHP_REDIS_API int redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
zval *z_tab, void *ctx)
{
return redis_mbulk_reply_zipped(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
z_tab, UNSERIALIZE_VALS, SCORE_DECODE_NONE);
}
PHP_REDIS_API void
redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
{
char *response;
int response_len;
zend_bool ret = 0;
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) != NULL) {
ret = (response[1] == '1');
efree(response);
}
IF_NOT_ATOMIC() {
add_next_index_bool(z_tab, ret);
} else {
RETURN_BOOL(ret);
}
}
PHP_REDIS_API void redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx) {
char *response;
int response_len;
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC))
== NULL)
{
IF_NOT_ATOMIC() {
add_next_index_bool(z_tab, 0);
return;
}
RETURN_FALSE;
}
IF_NOT_ATOMIC() {
zval zv, *z = &zv;
if (redis_unserialize(redis_sock, response, response_len, z TSRMLS_CC)) {
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z);
*z = zv;
#endif
add_next_index_zval(z_tab, z);
} else {
add_next_index_stringl(z_tab, response, response_len);
}
} else {
if (!redis_unserialize(redis_sock, response, response_len, return_value TSRMLS_CC)) {
RETVAL_STRINGL(response, response_len);
}
}
efree(response);
}
/* like string response, but never unserialized. */
PHP_REDIS_API void
redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
zval *z_tab, void *ctx)
{
char *response;
int response_len;
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC))
== NULL)
{
IF_NOT_ATOMIC() {
add_next_index_bool(z_tab, 0);
return;
}
RETURN_FALSE;
}
IF_NOT_ATOMIC() {
add_next_index_stringl(z_tab, response, response_len);
} else {
RETVAL_STRINGL(response, response_len);
}
efree(response);
}
/* Response for DEBUG object which is a formatted single line reply */
PHP_REDIS_API void redis_debug_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
zval *z_tab, void *ctx)
{
char *resp, *p, *p2, *p3, *p4;
int is_numeric, resp_len;
/* Add or return false if we can't read from the socket */
if((resp = redis_sock_read(redis_sock, &resp_len TSRMLS_CC))==NULL) {
IF_NOT_ATOMIC() {
add_next_index_bool(z_tab, 0);
return;
}
RETURN_FALSE;
}
zval zv, *z_result = &zv;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_result);
#endif
array_init(z_result);
/* Skip the '+' */
p = resp + 1;
/* : ... */
while((p2 = strchr(p, ':'))!=NULL) {
/* Null terminate at the ':' */
*p2++ = '\0';
/* Null terminate at the space if we have one */
if((p3 = strchr(p2, ' '))!=NULL) {
*p3++ = '\0';
} else {
p3 = resp + resp_len;
}
is_numeric = 1;
for(p4=p2; *p4; ++p4) {
if(*p4 < '0' || *p4 > '9') {
is_numeric = 0;
break;
}
}
/* Add our value */
if(is_numeric) {
add_assoc_long(z_result, p, atol(p2));
} else {
add_assoc_string(z_result, p, p2);
}
p = p3;
}
efree(resp);
IF_NOT_ATOMIC() {
add_next_index_zval(z_tab, z_result);
} else {
RETVAL_ZVAL(z_result, 0, 1);
}
}
/**
* redis_sock_create
*/
PHP_REDIS_API RedisSock*
redis_sock_create(char *host, int host_len, unsigned short port,
double timeout, double read_timeout,
int persistent, char *persistent_id,
long retry_interval, zend_bool lazy_connect)
{
RedisSock *redis_sock;
redis_sock = ecalloc(1, sizeof(RedisSock));
redis_sock->host = zend_string_init(host, host_len, 0);
redis_sock->stream = NULL;
redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED;
redis_sock->watching = 0;
redis_sock->dbNumber = 0;
redis_sock->retry_interval = retry_interval * 1000;
redis_sock->persistent = persistent;
redis_sock->lazy_connect = lazy_connect;
redis_sock->persistent_id = NULL;
if (persistent && persistent_id != NULL) {
redis_sock->persistent_id = zend_string_init(persistent_id, strlen(persistent_id), 0);
}
redis_sock->port = port;
redis_sock->timeout = timeout;
redis_sock->read_timeout = read_timeout;
redis_sock->serializer = REDIS_SERIALIZER_NONE;
redis_sock->mode = ATOMIC;
redis_sock->head = NULL;
redis_sock->current = NULL;
redis_sock->pipeline_cmd = NULL;
redis_sock->pipeline_len = 0;
redis_sock->err = NULL;
redis_sock->scan = REDIS_SCAN_NORETRY;
redis_sock->readonly = 0;
return redis_sock;
}
/**
* redis_sock_connect
*/
PHP_REDIS_API int redis_sock_connect(RedisSock *redis_sock TSRMLS_DC)
{
struct timeval tv, read_tv, *tv_ptr = NULL;
char host[1024], *persistent_id = NULL;
const char *fmtstr = "%s:%d";
int host_len, usocket = 0, err = 0;
php_netstream_data_t *sock;
int tcp_flag = 1;
if (redis_sock->stream != NULL) {
redis_sock_disconnect(redis_sock TSRMLS_CC);
}
tv.tv_sec = (time_t)redis_sock->timeout;
tv.tv_usec = (int)((redis_sock->timeout - tv.tv_sec) * 1000000);
if(tv.tv_sec != 0 || tv.tv_usec != 0) {
tv_ptr = &tv;
}
read_tv.tv_sec = (time_t)redis_sock->read_timeout;
read_tv.tv_usec = (int)((redis_sock->read_timeout-read_tv.tv_sec)*1000000);
if (ZSTR_VAL(redis_sock->host)[0] == '/' && redis_sock->port < 1) {
host_len = snprintf(host, sizeof(host), "unix://%s", ZSTR_VAL(redis_sock->host));
usocket = 1;
} else {
if(redis_sock->port == 0)
redis_sock->port = 6379;
#ifdef HAVE_IPV6
/* If we've got IPv6 and find a colon in our address, convert to proper
* IPv6 [host]:port format */
if (strchr(ZSTR_VAL(redis_sock->host), ':') != NULL) {
fmtstr = "[%s]:%d";
}
#endif
host_len = snprintf(host, sizeof(host), fmtstr, ZSTR_VAL(redis_sock->host), redis_sock->port);
}
if (redis_sock->persistent) {
if (redis_sock->persistent_id) {
spprintf(&persistent_id, 0, "phpredis:%s:%s", host,
ZSTR_VAL(redis_sock->persistent_id));
} else {
spprintf(&persistent_id, 0, "phpredis:%s:%f", host,
redis_sock->timeout);
}
}
redis_sock->stream = php_stream_xport_create(host, host_len,
0, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT,
persistent_id, tv_ptr, NULL, NULL, &err);
if (persistent_id) {
efree(persistent_id);
}
if (!redis_sock->stream) {
return -1;
}
/* Attempt to set TCP_NODELAY if we're not using a unix socket. */
sock = (php_netstream_data_t*)redis_sock->stream->abstract;
if (!usocket) {
err = setsockopt(sock->socket, IPPROTO_TCP, TCP_NODELAY, (char *) &tcp_flag, sizeof(int));
PHPREDIS_NOTUSED(err);
}
php_stream_auto_cleanup(redis_sock->stream);
if (read_tv.tv_sec != 0 || read_tv.tv_usec != 0) {
php_stream_set_option(redis_sock->stream,PHP_STREAM_OPTION_READ_TIMEOUT,
0, &read_tv);
}
php_stream_set_option(redis_sock->stream,
PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_NONE, NULL);
redis_sock->status = REDIS_SOCK_STATUS_CONNECTED;
return 0;
}
/**
* redis_sock_server_open
*/
PHP_REDIS_API int
redis_sock_server_open(RedisSock *redis_sock TSRMLS_DC)
{
int res = -1;
switch (redis_sock->status) {
case REDIS_SOCK_STATUS_DISCONNECTED:
return redis_sock_connect(redis_sock TSRMLS_CC);
case REDIS_SOCK_STATUS_CONNECTED:
res = 0;
break;
}
return res;
}
/**
* redis_sock_disconnect
*/
PHP_REDIS_API int redis_sock_disconnect(RedisSock *redis_sock TSRMLS_DC)
{
if (redis_sock == NULL) {
return 1;
}
redis_sock->dbNumber = 0;
if (redis_sock->stream != NULL) {
redis_sock->status = REDIS_SOCK_STATUS_DISCONNECTED;
redis_sock->watching = 0;
/* Stil valid? */
if (!redis_sock->persistent) {
php_stream_close(redis_sock->stream);
}
redis_sock->stream = NULL;
return 1;
}
return 0;
}
/**
* redis_sock_set_err
*/
PHP_REDIS_API void
redis_sock_set_err(RedisSock *redis_sock, const char *msg, int msg_len)
{
// Free our last error
if (redis_sock->err != NULL) {
zend_string_release(redis_sock->err);
redis_sock->err = NULL;
}
if (msg != NULL && msg_len > 0) {
// Copy in our new error message
redis_sock->err = zend_string_init(msg, msg_len, 0);
}
}
/**
* redis_sock_read_multibulk_reply
*/
PHP_REDIS_API int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, zval *z_tab,
void *ctx)
{
char inbuf[4096];
int numElems;
size_t len;
if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len TSRMLS_CC) < 0) {
return -1;
}
if(inbuf[0] != '*') {
IF_NOT_ATOMIC() {
add_next_index_bool(z_tab, 0);
} else {
if (inbuf[0] == '-') {
redis_sock_set_err(redis_sock, inbuf+1, len);
}
RETVAL_FALSE;
}
return -1;
}
numElems = atoi(inbuf+1);
zval zv, *z_multi_result = &zv;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_multi_result);
#endif
array_init(z_multi_result); /* pre-allocate array for multi's results. */
redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
z_multi_result, numElems, UNSERIALIZE_ALL);
IF_NOT_ATOMIC() {
add_next_index_zval(z_tab, z_multi_result);
} else {
RETVAL_ZVAL(z_multi_result, 0, 1);
}
/*zval_copy_ctor(return_value); */
return 0;
}
/* Like multibulk reply, but don't touch the values, they won't be unserialized
* (this is used by HKEYS). */
PHP_REDIS_API int
redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
{
char inbuf[4096];
int numElems;
size_t len;
if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len TSRMLS_CC) < 0) {
return -1;
}
if(inbuf[0] != '*') {
IF_NOT_ATOMIC() {
add_next_index_bool(z_tab, 0);
} else {
if (inbuf[0] == '-') {
redis_sock_set_err(redis_sock, inbuf+1, len);
}
RETVAL_FALSE;
}
return -1;
}
numElems = atoi(inbuf+1);
zval zv, *z_multi_result = &zv;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_multi_result);
#endif
array_init(z_multi_result); /* pre-allocate array for multi's results. */
redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
z_multi_result, numElems, UNSERIALIZE_NONE);
IF_NOT_ATOMIC() {
add_next_index_zval(z_tab, z_multi_result);
} else {
RETVAL_ZVAL(z_multi_result, 0, 1);
}
/*zval_copy_ctor(return_value); */
return 0;
}
PHP_REDIS_API void
redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
zval *z_tab, int count, int unserialize)
{
char *line;
int i, len;
for (i = 0; i < count; ++i) {
if ((line = redis_sock_read(redis_sock, &len TSRMLS_CC)) == NULL) {
add_next_index_bool(z_tab, 0);
continue;
}
/* We will attempt unserialization, if we're unserializing everything,
* or if we're unserializing keys and we're on a key, or we're
* unserializing values and we're on a value! */
int unwrap = (
(unserialize == UNSERIALIZE_ALL) ||
(unserialize == UNSERIALIZE_KEYS && i % 2 == 0) ||
(unserialize == UNSERIALIZE_VALS && i % 2 != 0)
);
zval zv, *z = &zv;
if (unwrap && redis_unserialize(redis_sock, line, len, z TSRMLS_CC)) {
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z);
*z = zv;
#endif
add_next_index_zval(z_tab, z);
} else {
add_next_index_stringl(z_tab, line, len);
}
efree(line);
}
}
/* Specialized multibulk processing for HMGET where we need to pair requested
* keys with their returned values */
PHP_REDIS_API int redis_mbulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx)
{
char inbuf[4096], *response;
int response_len;
int i, numElems;
size_t len;
zval *z_keys = ctx;
if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len TSRMLS_CC) < 0) {
return -1;
}
if(inbuf[0] != '*') {
IF_NOT_ATOMIC() {
add_next_index_bool(z_tab, 0);
} else {
RETVAL_FALSE;
}
return -1;
}
numElems = atoi(inbuf+1);
zval zv, *z_multi_result = &zv;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_multi_result);
#endif
array_init(z_multi_result); /* pre-allocate array for multi's results. */
for(i = 0; i < numElems; ++i) {
response = redis_sock_read(redis_sock, &response_len TSRMLS_CC);
if(response != NULL) {
zval zv0, *z = &zv0;
if (redis_unserialize(redis_sock, response, response_len, z TSRMLS_CC)) {
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z);
*z = zv0;
#endif
add_assoc_zval_ex(z_multi_result, Z_STRVAL(z_keys[i]), Z_STRLEN(z_keys[i]), z);
} else {
add_assoc_stringl_ex(z_multi_result, Z_STRVAL(z_keys[i]), Z_STRLEN(z_keys[i]), response, response_len);
}
efree(response);
} else {
add_assoc_bool_ex(z_multi_result, Z_STRVAL(z_keys[i]), Z_STRLEN(z_keys[i]), 0);
}
zval_dtor(&z_keys[i]);
}
efree(z_keys);
IF_NOT_ATOMIC() {
add_next_index_zval(z_tab, z_multi_result);
} else {
RETVAL_ZVAL(z_multi_result, 0, 1);
}
return 0;
}
/**
* redis_sock_write
*/
PHP_REDIS_API int
redis_sock_write(RedisSock *redis_sock, char *cmd, size_t sz TSRMLS_DC)
{
if (!redis_sock || redis_sock->status == REDIS_SOCK_STATUS_DISCONNECTED) {
zend_throw_exception(redis_exception_ce, "Connection closed",
0 TSRMLS_CC);
} else if (redis_check_eof(redis_sock, 0 TSRMLS_CC) == 0 &&
php_stream_write(redis_sock->stream, cmd, sz) == sz
) {
return sz;
}
return -1;
}
/**
* redis_free_socket
*/
PHP_REDIS_API void redis_free_socket(RedisSock *redis_sock)
{
if (redis_sock->prefix) {
zend_string_release(redis_sock->prefix);
}
if (redis_sock->pipeline_cmd) {
efree(redis_sock->pipeline_cmd);
}
if (redis_sock->err) {
zend_string_release(redis_sock->err);
}
if (redis_sock->auth) {
zend_string_release(redis_sock->auth);
}
if (redis_sock->persistent_id) {
zend_string_release(redis_sock->persistent_id);
}
if (redis_sock->host) {
zend_string_release(redis_sock->host);
}
efree(redis_sock);
}
PHP_REDIS_API int
redis_serialize(RedisSock *redis_sock, zval *z, char **val, strlen_t *val_len
TSRMLS_DC)
{
#if ZEND_MODULE_API_NO >= 20100000
php_serialize_data_t ht;
#else
HashTable ht;
#endif
smart_str sstr = {0};
#ifdef HAVE_REDIS_IGBINARY
size_t sz;
uint8_t *val8;
#endif
*val = NULL;
*val_len = 0;
switch(redis_sock->serializer) {
case REDIS_SERIALIZER_NONE:
switch(Z_TYPE_P(z)) {
case IS_STRING:
*val = Z_STRVAL_P(z);
*val_len = Z_STRLEN_P(z);
break;
case IS_OBJECT:
*val = "Object";
*val_len = 6;
break;
case IS_ARRAY:
*val = "Array";
*val_len = 5;
break;
default: { /* copy */
zend_string *zstr = zval_get_string(z);
*val = estrndup(ZSTR_VAL(zstr), ZSTR_LEN(zstr));
*val_len = ZSTR_LEN(zstr);
zend_string_release(zstr);
return 1;
}
}
break;
case REDIS_SERIALIZER_PHP:
#if ZEND_MODULE_API_NO >= 20100000
PHP_VAR_SERIALIZE_INIT(ht);
#else
zend_hash_init(&ht, 10, NULL, NULL, 0);
#endif
php_var_serialize(&sstr, z, &ht);
#if (PHP_MAJOR_VERSION < 7)
*val = estrndup(sstr.c, sstr.len);
*val_len = sstr.len;
#else
*val = estrndup(ZSTR_VAL(sstr.s), ZSTR_LEN(sstr.s));
*val_len = ZSTR_LEN(sstr.s);
#endif
smart_str_free(&sstr);
#if ZEND_MODULE_API_NO >= 20100000
PHP_VAR_SERIALIZE_DESTROY(ht);
#else
zend_hash_destroy(&ht);
#endif
return 1;
case REDIS_SERIALIZER_IGBINARY:
#ifdef HAVE_REDIS_IGBINARY
if(igbinary_serialize(&val8, (size_t *)&sz, z TSRMLS_CC) == 0) {
*val = (char*)val8;
*val_len = sz;
return 1;
}
#endif
break;
}
return 0;
}
PHP_REDIS_API int
redis_unserialize(RedisSock* redis_sock, const char *val, int val_len,
zval *z_ret TSRMLS_DC)
{
php_unserialize_data_t var_hash;
int ret = 0;
switch(redis_sock->serializer) {
case REDIS_SERIALIZER_PHP:
#if ZEND_MODULE_API_NO >= 20100000
PHP_VAR_UNSERIALIZE_INIT(var_hash);
#else
memset(&var_hash, 0, sizeof(var_hash));
#endif
if (php_var_unserialize(z_ret, (const unsigned char**)&val,
(const unsigned char*)val + val_len, &var_hash)
) {
ret = 1;
}
#if ZEND_MODULE_API_NO >= 20100000
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
#else
var_destroy(&var_hash);
#endif
break;
case REDIS_SERIALIZER_IGBINARY:
#ifdef HAVE_REDIS_IGBINARY
/*
* Check if the given string starts with an igbinary header.
*
* A modern igbinary string consists of the following format:
*
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
* | header (4) | type (1) | ... (n) | NUL (1) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
*
* With header being either 0x00000001 or 0x00000002
* (encoded as big endian).
*
* Not all versions contain the trailing NULL byte though, so
* do not check for that.
*/
if (val_len < 5
|| (memcmp(val, "\x00\x00\x00\x01", 4) != 0
&& memcmp(val, "\x00\x00\x00\x02", 4) != 0))
{
/* This is most definitely not an igbinary string, so do
not try to unserialize this as one. */
break;
}
#if (PHP_MAJOR_VERSION < 7)
INIT_PZVAL(z_ret);
ret = !igbinary_unserialize((const uint8_t *)val, (size_t)val_len, &z_ret TSRMLS_CC);
#else
ret = !igbinary_unserialize((const uint8_t *)val, (size_t)val_len, z_ret TSRMLS_CC);
#endif
#endif
break;
}
return ret;
}
PHP_REDIS_API int
redis_key_prefix(RedisSock *redis_sock, char **key, strlen_t *key_len) {
int ret_len;
char *ret;
if (redis_sock->prefix == NULL) {
return 0;
}
ret_len = ZSTR_LEN(redis_sock->prefix) + *key_len;
ret = ecalloc(1 + ret_len, 1);
memcpy(ret, ZSTR_VAL(redis_sock->prefix), ZSTR_LEN(redis_sock->prefix));
memcpy(ret + ZSTR_LEN(redis_sock->prefix), *key, *key_len);
*key = ret;
*key_len = ret_len;
return 1;
}
/*
* Processing for variant reply types (think EVAL)
*/
PHP_REDIS_API int
redis_sock_gets(RedisSock *redis_sock, char *buf, int buf_size,
size_t *line_size TSRMLS_DC)
{
// Handle EOF
if(-1 == redis_check_eof(redis_sock, 0 TSRMLS_CC)) {
return -1;
}
if(php_stream_get_line(redis_sock->stream, buf, buf_size, line_size)
== NULL)
{
// Close, put our socket state into error
REDIS_STREAM_CLOSE_MARK_FAILED(redis_sock);
// Throw a read error exception
zend_throw_exception(redis_exception_ce, "read error on connection",
0 TSRMLS_CC);
return -1;
}
/* We don't need \r\n */
*line_size-=2;
buf[*line_size]='\0';
/* Success! */
return 0;
}
PHP_REDIS_API int
redis_read_reply_type(RedisSock *redis_sock, REDIS_REPLY_TYPE *reply_type,
long *reply_info TSRMLS_DC)
{
// Make sure we haven't lost the connection, even trying to reconnect
if(-1 == redis_check_eof(redis_sock, 0 TSRMLS_CC)) {
// Failure
*reply_type = EOF;
return -1;
}
// Attempt to read the reply-type byte
if((*reply_type = php_stream_getc(redis_sock->stream)) == EOF) {
zend_throw_exception(redis_exception_ce, "socket error on read socket",
0 TSRMLS_CC);
return -1;
}
// If this is a BULK, MULTI BULK, or simply an INTEGER response, we can
// extract the value or size info here
if(*reply_type == TYPE_INT || *reply_type == TYPE_BULK ||
*reply_type == TYPE_MULTIBULK)
{
// Buffer to hold size information
char inbuf[255];
/* Read up to our newline */
if(php_stream_gets(redis_sock->stream, inbuf, sizeof(inbuf)) == NULL) {
return -1;
}
/* Set our size response */
*reply_info = atol(inbuf);
}
/* Success! */
return 0;
}
/*
* Read a single line response, having already consumed the reply-type byte
*/
PHP_REDIS_API int
redis_read_variant_line(RedisSock *redis_sock, REDIS_REPLY_TYPE reply_type,
zval *z_ret TSRMLS_DC)
{
// Buffer to read our single line reply
char inbuf[4096];
size_t line_size;
/* Attempt to read our single line reply */
if(redis_sock_gets(redis_sock, inbuf, sizeof(inbuf), &line_size TSRMLS_CC) < 0) {
return -1;
}
// If this is an error response, check if it is a SYNC error, and throw in
// that case
if(reply_type == TYPE_ERR) {
/* Set our last error */
redis_sock_set_err(redis_sock, inbuf, line_size);
/* Handle throwable errors */
redis_error_throw(redis_sock TSRMLS_CC);
/* Set our response to FALSE */
ZVAL_FALSE(z_ret);
} else {
/* Set our response to TRUE */
ZVAL_TRUE(z_ret);
}
return 0;
}
PHP_REDIS_API int
redis_read_variant_bulk(RedisSock *redis_sock, int size, zval *z_ret
TSRMLS_DC)
{
// Attempt to read the bulk reply
char *bulk_resp = redis_sock_read_bulk_reply(redis_sock, size TSRMLS_CC);
/* Set our reply to FALSE on failure, and the string on success */
if(bulk_resp == NULL) {
ZVAL_FALSE(z_ret);
return -1;
}
ZVAL_STRINGL(z_ret, bulk_resp, size);
efree(bulk_resp);
return 0;
}
PHP_REDIS_API int
redis_read_multibulk_recursive(RedisSock *redis_sock, int elements, zval *z_ret
TSRMLS_DC)
{
long reply_info;
REDIS_REPLY_TYPE reply_type;
zval zv, *z_subelem = &zv;
// Iterate while we have elements
while(elements > 0) {
// Attempt to read our reply type
if(redis_read_reply_type(redis_sock, &reply_type, &reply_info
TSRMLS_CC) < 0)
{
zend_throw_exception_ex(redis_exception_ce, 0 TSRMLS_CC,
"protocol error, couldn't parse MULTI-BULK response\n",
reply_type);
return -1;
}
// Switch on our reply-type byte
switch(reply_type) {
case TYPE_ERR:
case TYPE_LINE:
#if (PHP_MAJOR_VERSION < 7)
ALLOC_INIT_ZVAL(z_subelem);
#endif
redis_read_variant_line(redis_sock, reply_type, z_subelem
TSRMLS_CC);
add_next_index_zval(z_ret, z_subelem);
break;
case TYPE_INT:
// Add our long value
add_next_index_long(z_ret, reply_info);
break;
case TYPE_BULK:
// Init a zval for our bulk response, read and add it
#if (PHP_MAJOR_VERSION < 7)
ALLOC_INIT_ZVAL(z_subelem);
#endif
redis_read_variant_bulk(redis_sock, reply_info, z_subelem
TSRMLS_CC);
add_next_index_zval(z_ret, z_subelem);
break;
case TYPE_MULTIBULK:
// Construct an array for our sub element, and add it,
// and recurse
#if (PHP_MAJOR_VERSION < 7)
ALLOC_INIT_ZVAL(z_subelem);
#endif
array_init(z_subelem);
add_next_index_zval(z_ret, z_subelem);
redis_read_multibulk_recursive(redis_sock, reply_info,
z_subelem TSRMLS_CC);
break;
default:
// Stop the compiler from whinging
break;
}
/* Decrement our element counter */
elements--;
}
return 0;
}
PHP_REDIS_API int
redis_read_variant_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
zval *z_tab, void *ctx)
{
// Reply type, and reply size vars
REDIS_REPLY_TYPE reply_type;
long reply_info;
//char *bulk_resp;
// Attempt to read our header
if(redis_read_reply_type(redis_sock,&reply_type,&reply_info TSRMLS_CC) < 0)
{
return -1;
}
zval zv, *z_ret = &zv;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_ret);
#endif
/* Switch based on our top level reply type */
switch(reply_type) {
case TYPE_ERR:
case TYPE_LINE:
redis_read_variant_line(redis_sock, reply_type, z_ret TSRMLS_CC);
break;
case TYPE_INT:
ZVAL_LONG(z_ret, reply_info);
break;
case TYPE_BULK:
redis_read_variant_bulk(redis_sock, reply_info, z_ret TSRMLS_CC);
break;
case TYPE_MULTIBULK:
/* Initialize an array for our multi-bulk response */
array_init(z_ret);
// If we've got more than zero elements, parse our multi bulk
// response recursively
if(reply_info > -1) {
redis_read_multibulk_recursive(redis_sock, reply_info, z_ret
TSRMLS_CC);
}
break;
default:
#if (PHP_MAJOR_VERSION < 7)
efree(z_ret);
#endif
// Protocol error
zend_throw_exception_ex(redis_exception_ce, 0 TSRMLS_CC,
"protocol error, got '%c' as reply-type byte\n", reply_type);
return FAILURE;
}
IF_NOT_ATOMIC() {
add_next_index_zval(z_tab, z_ret);
} else {
/* Set our return value */
RETVAL_ZVAL(z_ret, 0, 1);
}
/* Success */
return 0;
}
/* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */
redis-3.1.6/library.h 0000644 0001750 0000012 00000014265 13223116600 015225 0 ustar pyatsukhnenko wheel #ifndef REDIS_LIBRARY_H
#define REDIS_LIBRARY_H
/* Non cluster command helper */
#define REDIS_SPPRINTF(ret, kw, fmt, ...) \
redis_spprintf(redis_sock, NULL TSRMLS_CC, ret, kw, fmt, ##__VA_ARGS__)
#define REDIS_CMD_APPEND_SSTR_STATIC(sstr, str) \
redis_cmd_append_sstr(sstr, str, sizeof(str)-1);
#define REDIS_CMD_APPEND_SSTR_OPT_STATIC(sstr, opt, str) \
if (opt) REDIS_CMD_APPEND_SSTR_STATIC(sstr, str);
#define REDIS_CMD_INIT_SSTR_STATIC(sstr, argc, keyword) \
redis_cmd_init_sstr(sstr, argc, keyword, sizeof(keyword)-1);
int redis_cmd_init_sstr(smart_string *str, int num_args, char *keyword, int keyword_len);
int redis_cmd_append_sstr(smart_string *str, char *append, int append_len);
int redis_cmd_append_sstr_int(smart_string *str, int append);
int redis_cmd_append_sstr_long(smart_string *str, long append);
int redis_cmd_append_sstr_dbl(smart_string *str, double value);
int redis_cmd_append_sstr_zval(smart_string *str, zval *z, RedisSock *redis_sock TSRMLS_DC);
int redis_cmd_append_sstr_key(smart_string *str, char *key, strlen_t len, RedisSock *redis_sock, short *slot);
PHP_REDIS_API int redis_spprintf(RedisSock *redis_sock, short *slot TSRMLS_DC, char **ret, char *kw, char *fmt, ...);
PHP_REDIS_API char * redis_sock_read(RedisSock *redis_sock, int *buf_len TSRMLS_DC);
PHP_REDIS_API int redis_sock_gets(RedisSock *redis_sock, char *buf, int buf_size, size_t* line_len TSRMLS_DC);
PHP_REDIS_API void redis_1_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API void redis_long_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval* z_tab, void *ctx);
typedef void (*SuccessCallback)(RedisSock *redis_sock);
PHP_REDIS_API void redis_boolean_response_impl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx, SuccessCallback success_callback);
PHP_REDIS_API void redis_boolean_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API void redis_bulk_double_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API void redis_string_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API void redis_ping_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API void redis_info_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API void redis_parse_info_response(char *response, zval *z_ret);
PHP_REDIS_API void redis_parse_client_list_response(char *response, zval *z_ret);
PHP_REDIS_API void redis_type_response(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API RedisSock* redis_sock_create(char *host, int host_len, unsigned short port, double timeout, double read_timeout, int persistent, char *persistent_id, long retry_interval, zend_bool lazy_connect);
PHP_REDIS_API int redis_sock_connect(RedisSock *redis_sock TSRMLS_DC);
PHP_REDIS_API int redis_sock_server_open(RedisSock *redis_sock TSRMLS_DC);
PHP_REDIS_API int redis_sock_disconnect(RedisSock *redis_sock TSRMLS_DC);
PHP_REDIS_API zval *redis_sock_read_multibulk_reply_zval(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab);
PHP_REDIS_API char *redis_sock_read_bulk_reply(RedisSock *redis_sock, int bytes TSRMLS_DC);
PHP_REDIS_API int redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *_z_tab, void *ctx);
PHP_REDIS_API void redis_mbulk_reply_loop(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, int count, int unserialize);
PHP_REDIS_API int redis_mbulk_reply_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API int redis_mbulk_reply_zipped_raw(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API int redis_mbulk_reply_zipped_vals(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API int redis_mbulk_reply_zipped_keys_int(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API int redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API int redis_mbulk_reply_assoc(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API int redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, REDIS_SCAN_TYPE type, zend_long *iter);
PHP_REDIS_API int redis_subscribe_response(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API int redis_unsubscribe_response(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API int redis_sock_write(RedisSock *redis_sock, char *cmd, size_t sz TSRMLS_DC);
PHP_REDIS_API void redis_stream_close(RedisSock *redis_sock TSRMLS_DC);
PHP_REDIS_API int redis_check_eof(RedisSock *redis_sock, int no_throw TSRMLS_DC);
PHP_REDIS_API RedisSock *redis_sock_get(zval *id TSRMLS_DC, int nothrow);
PHP_REDIS_API void redis_free_socket(RedisSock *redis_sock);
PHP_REDIS_API void redis_sock_set_err(RedisSock *redis_sock, const char *msg, int msg_len);
PHP_REDIS_API int
redis_serialize(RedisSock *redis_sock, zval *z, char **val, strlen_t *val_len TSRMLS_DC);
PHP_REDIS_API int
redis_key_prefix(RedisSock *redis_sock, char **key, strlen_t *key_len);
PHP_REDIS_API int
redis_unserialize(RedisSock *redis_sock, const char *val, int val_len, zval *z_ret TSRMLS_DC);
/*
* Variant Read methods, mostly to implement eval
*/
PHP_REDIS_API int redis_read_reply_type(RedisSock *redis_sock, REDIS_REPLY_TYPE *reply_type, long *reply_info TSRMLS_DC);
PHP_REDIS_API int redis_read_variant_line(RedisSock *redis_sock, REDIS_REPLY_TYPE reply_type, zval *z_ret TSRMLS_DC);
PHP_REDIS_API int redis_read_variant_bulk(RedisSock *redis_sock, int size, zval *z_ret TSRMLS_DC);
PHP_REDIS_API int redis_read_multibulk_recursive(RedisSock *redis_sock, int elements, zval *z_ret TSRMLS_DC);
PHP_REDIS_API int redis_read_variant_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API void redis_client_list_reply(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab);
#endif
redis-3.1.6/php_redis.h 0000644 0001750 0000012 00000017564 13223116600 015543 0 ustar pyatsukhnenko wheel /*
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2009 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Original author: Alfonso Jimenez |
| Maintainer: Nicolas Favre-Felix |
| Maintainer: Michael Grunder |
| Maintainer: Nasreddine Bouafif |
+----------------------------------------------------------------------+
*/
#include "common.h"
#ifndef PHP_REDIS_H
#define PHP_REDIS_H
/* phpredis version */
#define PHP_REDIS_VERSION "3.1.6"
PHP_METHOD(Redis, __construct);
PHP_METHOD(Redis, __destruct);
PHP_METHOD(Redis, connect);
PHP_METHOD(Redis, pconnect);
PHP_METHOD(Redis, close);
PHP_METHOD(Redis, ping);
PHP_METHOD(Redis, echo);
PHP_METHOD(Redis, get);
PHP_METHOD(Redis, set);
PHP_METHOD(Redis, setex);
PHP_METHOD(Redis, psetex);
PHP_METHOD(Redis, setnx);
PHP_METHOD(Redis, getSet);
PHP_METHOD(Redis, randomKey);
PHP_METHOD(Redis, renameKey);
PHP_METHOD(Redis, renameNx);
PHP_METHOD(Redis, getMultiple);
PHP_METHOD(Redis, exists);
PHP_METHOD(Redis, delete);
PHP_METHOD(Redis, incr);
PHP_METHOD(Redis, incrBy);
PHP_METHOD(Redis, incrByFloat);
PHP_METHOD(Redis, decr);
PHP_METHOD(Redis, decrBy);
PHP_METHOD(Redis, type);
PHP_METHOD(Redis, append);
PHP_METHOD(Redis, getRange);
PHP_METHOD(Redis, setRange);
PHP_METHOD(Redis, getBit);
PHP_METHOD(Redis, setBit);
PHP_METHOD(Redis, strlen);
PHP_METHOD(Redis, getKeys);
PHP_METHOD(Redis, sort);
PHP_METHOD(Redis, sortAsc);
PHP_METHOD(Redis, sortAscAlpha);
PHP_METHOD(Redis, sortDesc);
PHP_METHOD(Redis, sortDescAlpha);
PHP_METHOD(Redis, lPush);
PHP_METHOD(Redis, lPushx);
PHP_METHOD(Redis, rPush);
PHP_METHOD(Redis, rPushx);
PHP_METHOD(Redis, lPop);
PHP_METHOD(Redis, rPop);
PHP_METHOD(Redis, blPop);
PHP_METHOD(Redis, brPop);
PHP_METHOD(Redis, lSize);
PHP_METHOD(Redis, lRemove);
PHP_METHOD(Redis, listTrim);
PHP_METHOD(Redis, lGet);
PHP_METHOD(Redis, lGetRange);
PHP_METHOD(Redis, lSet);
PHP_METHOD(Redis, lInsert);
PHP_METHOD(Redis, sAdd);
PHP_METHOD(Redis, sAddArray);
PHP_METHOD(Redis, sSize);
PHP_METHOD(Redis, sRemove);
PHP_METHOD(Redis, sMove);
PHP_METHOD(Redis, sPop);
PHP_METHOD(Redis, sRandMember);
PHP_METHOD(Redis, sContains);
PHP_METHOD(Redis, sMembers);
PHP_METHOD(Redis, sInter);
PHP_METHOD(Redis, sInterStore);
PHP_METHOD(Redis, sUnion);
PHP_METHOD(Redis, sUnionStore);
PHP_METHOD(Redis, sDiff);
PHP_METHOD(Redis, sDiffStore);
PHP_METHOD(Redis, setTimeout);
PHP_METHOD(Redis, pexpire);
PHP_METHOD(Redis, save);
PHP_METHOD(Redis, bgSave);
PHP_METHOD(Redis, lastSave);
PHP_METHOD(Redis, flushDB);
PHP_METHOD(Redis, flushAll);
PHP_METHOD(Redis, dbSize);
PHP_METHOD(Redis, auth);
PHP_METHOD(Redis, ttl);
PHP_METHOD(Redis, pttl);
PHP_METHOD(Redis, persist);
PHP_METHOD(Redis, info);
PHP_METHOD(Redis, select);
PHP_METHOD(Redis, move);
PHP_METHOD(Redis, zAdd);
PHP_METHOD(Redis, zDelete);
PHP_METHOD(Redis, zRange);
PHP_METHOD(Redis, zRevRange);
PHP_METHOD(Redis, zRangeByScore);
PHP_METHOD(Redis, zRevRangeByScore);
PHP_METHOD(Redis, zRangeByLex);
PHP_METHOD(Redis, zRevRangeByLex);
PHP_METHOD(Redis, zRemRangeByLex);
PHP_METHOD(Redis, zLexCount);
PHP_METHOD(Redis, zCount);
PHP_METHOD(Redis, zDeleteRangeByScore);
PHP_METHOD(Redis, zDeleteRangeByRank);
PHP_METHOD(Redis, zCard);
PHP_METHOD(Redis, zScore);
PHP_METHOD(Redis, zRank);
PHP_METHOD(Redis, zRevRank);
PHP_METHOD(Redis, zIncrBy);
PHP_METHOD(Redis, zInter);
PHP_METHOD(Redis, zUnion);
PHP_METHOD(Redis, expireAt);
PHP_METHOD(Redis, pexpireAt);
PHP_METHOD(Redis, bgrewriteaof);
PHP_METHOD(Redis, slaveof);
PHP_METHOD(Redis, object);
PHP_METHOD(Redis, bitop);
PHP_METHOD(Redis, bitcount);
PHP_METHOD(Redis, bitpos);
PHP_METHOD(Redis, eval);
PHP_METHOD(Redis, evalsha);
PHP_METHOD(Redis, script);
PHP_METHOD(Redis, debug);
PHP_METHOD(Redis, dump);
PHP_METHOD(Redis, restore);
PHP_METHOD(Redis, migrate);
PHP_METHOD(Redis, time);
PHP_METHOD(Redis, role);
PHP_METHOD(Redis, getLastError);
PHP_METHOD(Redis, clearLastError);
PHP_METHOD(Redis, _prefix);
PHP_METHOD(Redis, _serialize);
PHP_METHOD(Redis, _unserialize);
PHP_METHOD(Redis, mset);
PHP_METHOD(Redis, msetnx);
PHP_METHOD(Redis, rpoplpush);
PHP_METHOD(Redis, brpoplpush);
PHP_METHOD(Redis, hGet);
PHP_METHOD(Redis, hSet);
PHP_METHOD(Redis, hSetNx);
PHP_METHOD(Redis, hDel);
PHP_METHOD(Redis, hLen);
PHP_METHOD(Redis, hKeys);
PHP_METHOD(Redis, hVals);
PHP_METHOD(Redis, hGetAll);
PHP_METHOD(Redis, hExists);
PHP_METHOD(Redis, hIncrBy);
PHP_METHOD(Redis, hIncrByFloat);
PHP_METHOD(Redis, hMset);
PHP_METHOD(Redis, hMget);
PHP_METHOD(Redis, hStrLen);
PHP_METHOD(Redis, multi);
PHP_METHOD(Redis, discard);
PHP_METHOD(Redis, exec);
PHP_METHOD(Redis, watch);
PHP_METHOD(Redis, unwatch);
PHP_METHOD(Redis, pipeline);
PHP_METHOD(Redis, publish);
PHP_METHOD(Redis, subscribe);
PHP_METHOD(Redis, psubscribe);
PHP_METHOD(Redis, unsubscribe);
PHP_METHOD(Redis, punsubscribe);
PHP_METHOD(Redis, getOption);
PHP_METHOD(Redis, setOption);
PHP_METHOD(Redis, config);
PHP_METHOD(Redis, slowlog);
PHP_METHOD(Redis, wait);
PHP_METHOD(Redis, pubsub);
/* Geoadd and friends */
PHP_METHOD(Redis, geoadd);
PHP_METHOD(Redis, geohash);
PHP_METHOD(Redis, geopos);
PHP_METHOD(Redis, geodist);
PHP_METHOD(Redis, georadius);
PHP_METHOD(Redis, georadiusbymember);
PHP_METHOD(Redis, client);
PHP_METHOD(Redis, command);
PHP_METHOD(Redis, rawcommand);
/* SCAN and friends */
PHP_METHOD(Redis, scan);
PHP_METHOD(Redis, hscan);
PHP_METHOD(Redis, sscan);
PHP_METHOD(Redis, zscan);
/* HyperLogLog commands */
PHP_METHOD(Redis, pfadd);
PHP_METHOD(Redis, pfcount);
PHP_METHOD(Redis, pfmerge);
/* Reflection */
PHP_METHOD(Redis, getHost);
PHP_METHOD(Redis, getPort);
PHP_METHOD(Redis, getDBNum);
PHP_METHOD(Redis, getTimeout);
PHP_METHOD(Redis, getReadTimeout);
PHP_METHOD(Redis, isConnected);
PHP_METHOD(Redis, getPersistentID);
PHP_METHOD(Redis, getAuth);
PHP_METHOD(Redis, getMode);
#ifdef ZTS
#include "TSRM.h"
#endif
PHP_MINIT_FUNCTION(redis);
PHP_MSHUTDOWN_FUNCTION(redis);
PHP_MINFO_FUNCTION(redis);
/* Redis response handler function callback prototype */
typedef void (*ResultCallback)(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, zval *z_tab, void *ctx);
PHP_REDIS_API int redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent);
PHP_REDIS_API void generic_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, char *sub_cmd);
PHP_REDIS_API void generic_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS,
char *unsub_cmd);
PHP_REDIS_API int redis_response_enqueued(RedisSock *redis_sock TSRMLS_DC);
PHP_REDIS_API int get_flag(zval *object TSRMLS_DC);
PHP_REDIS_API void set_flag(zval *object, int new_flag TSRMLS_DC);
PHP_REDIS_API int redis_sock_read_multibulk_multi_reply_loop(
INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, zval *z_tab,
int numElems);
#ifndef _MSC_VER
ZEND_BEGIN_MODULE_GLOBALS(redis)
ZEND_END_MODULE_GLOBALS(redis)
#endif
extern zend_module_entry redis_module_entry;
#define redis_module_ptr &redis_module_entry
#define phpext_redis_ptr redis_module_ptr
#endif
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: noet sw=4 ts=4 fdm=marker
* vim<600: noet sw=4 ts=4
*/
redis-3.1.6/redis_array.c 0000644 0001750 0000012 00000114446 13223116600 016062 0 ustar pyatsukhnenko wheel /*
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2009 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: Nicolas Favre-Felix |
| Maintainer: Michael Grunder |
+----------------------------------------------------------------------+
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "common.h"
#include "ext/standard/info.h"
#include "php_ini.h"
#include "php_redis.h"
#include
#include "library.h"
#include "redis_array.h"
#include "redis_array_impl.h"
/* Simple macro to detect failure in a RedisArray call */
#define RA_CALL_FAILED(rv, cmd) ( \
PHPREDIS_ZVAL_IS_STRICT_FALSE(rv) || \
(Z_TYPE_P(rv) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(rv)) == 0) || \
(Z_TYPE_P(rv) == IS_LONG && Z_LVAL_P(rv) == 0 && !strcasecmp(cmd, "TYPE")) \
)
extern zend_class_entry *redis_ce;
zend_class_entry *redis_array_ce;
ZEND_BEGIN_ARG_INFO_EX(arginfo_ctor, 0, 0, 1)
ZEND_ARG_INFO(0, name_or_hosts)
ZEND_ARG_ARRAY_INFO(0, options, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_call, 0, 0, 2)
ZEND_ARG_INFO(0, function_name)
ZEND_ARG_INFO(0, arguments)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_target, 0, 0, 1)
ZEND_ARG_INFO(0, key)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_instance, 0, 0, 1)
ZEND_ARG_INFO(0, host)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_rehash, 0, 0, 0)
ZEND_ARG_INFO(0, callable)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_select, 0, 0, 1)
ZEND_ARG_INFO(0, index)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_mget, 0, 0, 1)
ZEND_ARG_INFO(0, keys)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_mset, 0, 0, 1)
ZEND_ARG_INFO(0, pairs)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_del, 0, 0, 1)
ZEND_ARG_INFO(0, keys)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_getopt, 0, 0, 1)
ZEND_ARG_INFO(0, opt)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_setopt, 0, 0, 2)
ZEND_ARG_INFO(0, opt)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_keys, 0, 0, 1)
ZEND_ARG_INFO(0, pattern)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_multi, 0, 0, 1)
ZEND_ARG_INFO(0, host)
ZEND_ARG_INFO(0, mode)
ZEND_END_ARG_INFO()
zend_function_entry redis_array_functions[] = {
PHP_ME(RedisArray, __construct, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, __call, arginfo_call, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, _hosts, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, _target, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, _instance, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, _function, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, _distributor, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, _rehash, NULL, ZEND_ACC_PUBLIC)
/* special implementation for a few functions */
PHP_ME(RedisArray, select, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, info, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, ping, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, flushdb, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, flushall, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, mget, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, mset, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, del, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, getOption, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, setOption,NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, keys, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, save, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, bgsave, NULL, ZEND_ACC_PUBLIC)
/* Multi/Exec */
PHP_ME(RedisArray, multi, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, exec, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, discard, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisArray, unwatch, NULL, ZEND_ACC_PUBLIC)
/* Aliases */
PHP_MALIAS(RedisArray, delete, del, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(RedisArray, getMultiple, mget, NULL, ZEND_ACC_PUBLIC)
PHP_FE_END
};
static void
redis_array_free(RedisArray *ra)
{
int i;
/* Redis objects */
for(i=0;icount;i++) {
zval_dtor(&ra->redis[i]);
efree(ra->hosts[i]);
}
efree(ra->redis);
efree(ra->hosts);
/* delete hash function */
zval_dtor(&ra->z_fun);
/* Distributor */
zval_dtor(&ra->z_dist);
/* Delete pur commands */
zend_hash_destroy(ra->pure_cmds);
FREE_HASHTABLE(ra->pure_cmds);
/* Free structure itself */
efree(ra);
}
#if (PHP_MAJOR_VERSION < 7)
typedef struct {
zend_object std;
RedisArray *ra;
} redis_array_object;
void
free_redis_array_object(void *object TSRMLS_DC)
{
redis_array_object *obj = (redis_array_object *)object;
zend_object_std_dtor(&obj->std TSRMLS_CC);
if (obj->ra) {
if (obj->ra->prev) redis_array_free(obj->ra->prev);
redis_array_free(obj->ra);
}
efree(obj);
}
zend_object_value
create_redis_array_object(zend_class_entry *ce TSRMLS_DC)
{
zend_object_value retval;
redis_array_object *obj = ecalloc(1, sizeof(redis_array_object));
memset(obj, 0, sizeof(redis_array_object));
zend_object_std_init(&obj->std, ce TSRMLS_CC);
#if PHP_VERSION_ID < 50399
zval *tmp;
zend_hash_copy(obj->std.properties, &ce->default_properties,
(copy_ctor_func_t)zval_add_ref, (void *)&tmp, sizeof(zval *));
#else
object_properties_init(&obj->std, ce);
#endif
retval.handle = zend_objects_store_put(obj,
(zend_objects_store_dtor_t)zend_objects_destroy_object,
(zend_objects_free_object_storage_t)free_redis_array_object,
NULL TSRMLS_CC);
retval.handlers = zend_get_std_object_handlers();
return retval;
}
#else
typedef struct {
RedisArray *ra;
zend_object std;
} redis_array_object;
zend_object_handlers redis_array_object_handlers;
void
free_redis_array_object(zend_object *object)
{
redis_array_object *obj = (redis_array_object *)((char *)(object) - XtOffsetOf(redis_array_object, std));
if (obj->ra) {
if (obj->ra->prev) redis_array_free(obj->ra->prev);
redis_array_free(obj->ra);
}
zend_object_std_dtor(&obj->std TSRMLS_CC);
}
zend_object *
create_redis_array_object(zend_class_entry *ce TSRMLS_DC)
{
redis_array_object *obj = ecalloc(1, sizeof(redis_array_object) + zend_object_properties_size(ce));
obj->ra = NULL;
zend_object_std_init(&obj->std, ce TSRMLS_CC);
object_properties_init(&obj->std, ce);
memcpy(&redis_array_object_handlers, zend_get_std_object_handlers(), sizeof(redis_array_object_handlers));
redis_array_object_handlers.offset = XtOffsetOf(redis_array_object, std);
redis_array_object_handlers.free_obj = free_redis_array_object;
obj->std.handlers = &redis_array_object_handlers;
return &obj->std;
}
#endif
/**
* redis_array_get
*/
PHP_REDIS_API RedisArray *
redis_array_get(zval *id TSRMLS_DC)
{
redis_array_object *obj;
if (Z_TYPE_P(id) == IS_OBJECT) {
#if (PHP_MAJOR_VERSION < 7)
obj = (redis_array_object *)zend_objects_get_address(id TSRMLS_CC);
#else
obj = (redis_array_object *)((char *)Z_OBJ_P(id) - XtOffsetOf(redis_array_object, std));
#endif
return obj->ra;
}
return NULL;
}
/* {{{ proto RedisArray RedisArray::__construct()
Public constructor */
PHP_METHOD(RedisArray, __construct)
{
zval *z0, z_fun, z_dist, *zpData, *z_opts = NULL;
RedisArray *ra = NULL;
zend_bool b_index = 0, b_autorehash = 0, b_pconnect = 0;
HashTable *hPrev = NULL, *hOpts = NULL;
long l_retry_interval = 0;
zend_bool b_lazy_connect = 0;
double d_connect_timeout = 0, read_timeout = 0.0;
redis_array_object *obj;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|a", &z0, &z_opts) == FAILURE) {
RETURN_FALSE;
}
ZVAL_NULL(&z_fun);
ZVAL_NULL(&z_dist);
/* extract options */
if(z_opts) {
hOpts = Z_ARRVAL_P(z_opts);
/* extract previous ring. */
if ((zpData = zend_hash_str_find(hOpts, "previous", sizeof("previous") - 1)) != NULL && Z_TYPE_P(zpData) == IS_ARRAY
&& zend_hash_num_elements(Z_ARRVAL_P(zpData)) != 0
) {
/* consider previous array as non-existent if empty. */
hPrev = Z_ARRVAL_P(zpData);
}
/* extract function name. */
if ((zpData = zend_hash_str_find(hOpts, "function", sizeof("function") - 1)) != NULL) {
ZVAL_ZVAL(&z_fun, zpData, 1, 0);
}
/* extract function name. */
if ((zpData = zend_hash_str_find(hOpts, "distributor", sizeof("distributor") - 1)) != NULL) {
ZVAL_ZVAL(&z_dist, zpData, 1, 0);
}
/* extract index option. */
if ((zpData = zend_hash_str_find(hOpts, "index", sizeof("index") - 1)) != NULL) {
b_index = zval_is_true(zpData);
}
/* extract autorehash option. */
if ((zpData = zend_hash_str_find(hOpts, "autorehash", sizeof("autorehash") - 1)) != NULL) {
b_autorehash = zval_is_true(zpData);
}
/* pconnect */
if ((zpData = zend_hash_str_find(hOpts, "pconnect", sizeof("pconnect") - 1)) != NULL) {
b_pconnect = zval_is_true(zpData);
}
/* extract retry_interval option. */
if ((zpData = zend_hash_str_find(hOpts, "retry_interval", sizeof("retry_interval") - 1)) != NULL) {
if (Z_TYPE_P(zpData) == IS_LONG) {
l_retry_interval = Z_LVAL_P(zpData);
} else if (Z_TYPE_P(zpData) == IS_STRING) {
l_retry_interval = atol(Z_STRVAL_P(zpData));
}
}
/* extract lazy connect option. */
if ((zpData = zend_hash_str_find(hOpts, "lazy_connect", sizeof("lazy_connect") - 1)) != NULL) {
b_lazy_connect = zval_is_true(zpData);
}
/* extract connect_timeout option */
if ((zpData = zend_hash_str_find(hOpts, "connect_timeout", sizeof("connect_timeout") - 1)) != NULL) {
if (Z_TYPE_P(zpData) == IS_DOUBLE) {
d_connect_timeout = Z_DVAL_P(zpData);
} else if (Z_TYPE_P(zpData) == IS_LONG) {
d_connect_timeout = Z_LVAL_P(zpData);
} else if (Z_TYPE_P(zpData) == IS_STRING) {
d_connect_timeout = atof(Z_STRVAL_P(zpData));
}
}
/* extract read_timeout option */
if ((zpData = zend_hash_str_find(hOpts, "read_timeout", sizeof("read_timeout") - 1)) != NULL) {
if (Z_TYPE_P(zpData) == IS_DOUBLE) {
read_timeout = Z_DVAL_P(zpData);
} else if (Z_TYPE_P(zpData) == IS_LONG) {
read_timeout = Z_LVAL_P(zpData);
} else if (Z_TYPE_P(zpData) == IS_STRING) {
read_timeout = atof(Z_STRVAL_P(zpData));
}
}
}
/* extract either name of list of hosts from z0 */
switch(Z_TYPE_P(z0)) {
case IS_STRING:
ra = ra_load_array(Z_STRVAL_P(z0) TSRMLS_CC);
break;
case IS_ARRAY:
ra = ra_make_array(Z_ARRVAL_P(z0), &z_fun, &z_dist, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect, d_connect_timeout, read_timeout TSRMLS_CC);
break;
default:
WRONG_PARAM_COUNT;
}
zval_dtor(&z_dist);
zval_dtor(&z_fun);
if(ra) {
ra->auto_rehash = b_autorehash;
ra->connect_timeout = d_connect_timeout;
if(ra->prev) ra->prev->auto_rehash = b_autorehash;
#if (PHP_MAJOR_VERSION < 7)
obj = (redis_array_object *)zend_objects_get_address(getThis() TSRMLS_CC);
#else
obj = (redis_array_object *)((char *)Z_OBJ_P(getThis()) - XtOffsetOf(redis_array_object, std));
#endif
obj->ra = ra;
}
}
static void
ra_forward_call(INTERNAL_FUNCTION_PARAMETERS, RedisArray *ra, const char *cmd, int cmd_len, zval *z_args, zval *z_new_target) {
zval z_fun, *redis_inst, *z_callargs, *zp_tmp;
char *key = NULL; /* set to avoid "unused-but-set-variable" */
int i, key_len = 0, argc;
HashTable *h_args;
zend_bool b_write_cmd = 0;
h_args = Z_ARRVAL_P(z_args);
if ((argc = zend_hash_num_elements(h_args)) == 0) {
RETURN_FALSE;
}
if(ra->z_multi_exec) {
redis_inst = ra->z_multi_exec; /* we already have the instance */
} else {
/* extract key and hash it. */
if ((zp_tmp = zend_hash_index_find(h_args, 0)) == NULL || Z_TYPE_P(zp_tmp) != IS_STRING) {
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not find key");
RETURN_FALSE;
}
key = Z_STRVAL_P(zp_tmp);
key_len = Z_STRLEN_P(zp_tmp);
/* find node */
redis_inst = ra_find_node(ra, key, key_len, NULL TSRMLS_CC);
if(!redis_inst) {
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not find any redis servers for this key.");
RETURN_FALSE;
}
}
/* pass call through */
ZVAL_STRINGL(&z_fun, cmd, cmd_len); /* method name */
z_callargs = ecalloc(argc, sizeof(zval));
/* copy args to array */
i = 0;
ZEND_HASH_FOREACH_VAL(h_args, zp_tmp) {
ZVAL_ZVAL(&z_callargs[i], zp_tmp, 1, 0);
i++;
} ZEND_HASH_FOREACH_END();
/* multi/exec */
if(ra->z_multi_exec) {
call_user_function(&redis_ce->function_table, ra->z_multi_exec, &z_fun, return_value, argc, z_callargs);
zval_dtor(return_value);
zval_dtor(&z_fun);
for (i = 0; i < argc; ++i) {
zval_dtor(&z_callargs[i]);
}
efree(z_callargs);
RETURN_ZVAL(getThis(), 1, 0);
}
/* check if write cmd */
b_write_cmd = ra_is_write_cmd(ra, cmd, cmd_len);
/* CALL! */
if(ra->index && b_write_cmd) {
/* add MULTI + SADD */
ra_index_multi(redis_inst, MULTI TSRMLS_CC);
/* call using discarded temp value and extract exec results after. */
call_user_function(&redis_ce->function_table, redis_inst, &z_fun, return_value, argc, z_callargs);
zval_dtor(return_value);
/* add keys to index. */
ra_index_key(key, key_len, redis_inst TSRMLS_CC);
/* call EXEC */
ra_index_exec(redis_inst, return_value, 0 TSRMLS_CC);
} else { /* call directly through. */
call_user_function(&redis_ce->function_table, redis_inst, &z_fun, return_value, argc, z_callargs);
if (!b_write_cmd) {
/* check if we have an error. */
if (ra->prev && RA_CALL_FAILED(return_value, cmd)) { /* there was an error reading, try with prev ring. */
/* Free previous return value */
zval_dtor(return_value);
/* ERROR, FALLBACK TO PREVIOUS RING and forward a reference to the first redis instance we were looking at. */
ra_forward_call(INTERNAL_FUNCTION_PARAM_PASSTHRU, ra->prev, cmd, cmd_len, z_args, z_new_target ? z_new_target : redis_inst);
}
/* Autorehash if the key was found on the previous node if this is a read command and auto rehashing is on */
if (ra->auto_rehash && z_new_target && !RA_CALL_FAILED(return_value, cmd)) { /* move key from old ring to new ring */
ra_move_key(key, key_len, redis_inst, z_new_target TSRMLS_CC);
}
}
}
/* cleanup */
zval_dtor(&z_fun);
for (i = 0; i < argc; ++i) {
zval_dtor(&z_callargs[i]);
}
efree(z_callargs);
}
PHP_METHOD(RedisArray, __call)
{
zval *object;
RedisArray *ra;
zval *z_args;
char *cmd;
strlen_t cmd_len;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osa",
&object, redis_array_ce, &cmd, &cmd_len, &z_args) == FAILURE) {
RETURN_FALSE;
}
if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
ra_forward_call(INTERNAL_FUNCTION_PARAM_PASSTHRU, ra, cmd, cmd_len, z_args, NULL);
}
PHP_METHOD(RedisArray, _hosts)
{
zval *object;
int i;
RedisArray *ra;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O",
&object, redis_array_ce) == FAILURE) {
RETURN_FALSE;
}
if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
array_init(return_value);
for(i = 0; i < ra->count; ++i) {
add_next_index_string(return_value, ra->hosts[i]);
}
}
PHP_METHOD(RedisArray, _target)
{
zval *object;
RedisArray *ra;
char *key;
strlen_t key_len;
zval *redis_inst;
int i;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os",
&object, redis_array_ce, &key, &key_len) == FAILURE) {
RETURN_FALSE;
}
if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
redis_inst = ra_find_node(ra, key, key_len, &i TSRMLS_CC);
if(redis_inst) {
RETURN_STRING(ra->hosts[i]);
} else {
RETURN_NULL();
}
}
PHP_METHOD(RedisArray, _instance)
{
zval *object;
RedisArray *ra;
char *target;
strlen_t target_len;
zval *z_redis;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os",
&object, redis_array_ce, &target, &target_len) == FAILURE) {
RETURN_FALSE;
}
if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
z_redis = ra_find_node_by_name(ra, target, target_len TSRMLS_CC);
if(z_redis) {
RETURN_ZVAL(z_redis, 1, 0);
} else {
RETURN_NULL();
}
}
PHP_METHOD(RedisArray, _function)
{
zval *object, *z_fun;
RedisArray *ra;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O",
&object, redis_array_ce) == FAILURE) {
RETURN_FALSE;
}
if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
z_fun = &ra->z_fun;
RETURN_ZVAL(z_fun, 1, 0);
}
PHP_METHOD(RedisArray, _distributor)
{
zval *object, *z_dist;
RedisArray *ra;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O",
&object, redis_array_ce) == FAILURE) {
RETURN_FALSE;
}
if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
z_dist = &ra->z_dist;
RETURN_ZVAL(z_dist, 1, 0);
}
PHP_METHOD(RedisArray, _rehash)
{
zval *object;
RedisArray *ra;
zend_fcall_info z_cb = {0};
zend_fcall_info_cache z_cb_cache = {0};
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O|f",
&object, redis_array_ce, &z_cb, &z_cb_cache) == FAILURE) {
RETURN_FALSE;
}
if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
if (ZEND_NUM_ARGS() == 0) {
ra_rehash(ra, NULL, NULL TSRMLS_CC);
} else {
ra_rehash(ra, &z_cb, &z_cb_cache TSRMLS_CC);
}
}
static void multihost_distribute(INTERNAL_FUNCTION_PARAMETERS, const char *method_name)
{
zval *object, z_fun;
int i;
RedisArray *ra;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O",
&object, redis_array_ce) == FAILURE) {
RETURN_FALSE;
}
if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
/* prepare call */
ZVAL_STRING(&z_fun, method_name);
array_init(return_value);
for(i = 0; i < ra->count; ++i) {
zval zv, *z_tmp = &zv;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_tmp);
#endif
/* Call each node in turn */
call_user_function(&redis_ce->function_table, &ra->redis[i], &z_fun, z_tmp, 0, NULL);
add_assoc_zval(return_value, ra->hosts[i], z_tmp);
}
zval_dtor(&z_fun);
}
PHP_METHOD(RedisArray, info)
{
multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "INFO");
}
PHP_METHOD(RedisArray, ping)
{
multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PING");
}
PHP_METHOD(RedisArray, flushdb)
{
multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "FLUSHDB");
}
PHP_METHOD(RedisArray, flushall)
{
multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "FLUSHALL");
}
PHP_METHOD(RedisArray, save)
{
multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SAVE");
}
PHP_METHOD(RedisArray, bgsave)
{
multihost_distribute(INTERNAL_FUNCTION_PARAM_PASSTHRU, "BGSAVE");
}
PHP_METHOD(RedisArray, keys)
{
zval *object, z_args[1], z_fun;
RedisArray *ra;
char *pattern;
strlen_t pattern_len;
int i;
/* Make sure the prototype is correct */
if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os",
&object, redis_array_ce, &pattern, &pattern_len) == FAILURE)
{
RETURN_FALSE;
}
/* Make sure we can grab our RedisArray object */
if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
/* Set up our function call (KEYS) */
ZVAL_STRINGL(&z_fun, "KEYS", 4);
/* We will be passing with one string argument (the pattern) */
ZVAL_STRINGL(z_args, pattern, pattern_len);
/* Init our array return */
array_init(return_value);
/* Iterate our RedisArray nodes */
for(i=0; icount; ++i) {
zval zv, *z_tmp = &zv;
#if (PHP_MAJOR_VERSION < 7)
/* Return for this node */
MAKE_STD_ZVAL(z_tmp);
#endif
/* Call KEYS on each node */
call_user_function(&redis_ce->function_table, &ra->redis[i], &z_fun, z_tmp, 1, z_args);
/* Add the result for this host */
add_assoc_zval(return_value, ra->hosts[i], z_tmp);
}
zval_dtor(&z_args[0]);
zval_dtor(&z_fun);
}
PHP_METHOD(RedisArray, getOption)
{
zval *object, z_fun, z_args[1];
int i;
RedisArray *ra;
zend_long opt;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol",
&object, redis_array_ce, &opt) == FAILURE) {
RETURN_FALSE;
}
if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
/* prepare call */
ZVAL_STRINGL(&z_fun, "getOption", 9);
/* copy arg */
ZVAL_LONG(&z_args[0], opt);
array_init(return_value);
for(i = 0; i < ra->count; ++i) {
zval zv, *z_tmp = &zv;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_tmp);
#endif
/* Call each node in turn */
call_user_function(&redis_ce->function_table, &ra->redis[i], &z_fun, z_tmp, 1, z_args);
add_assoc_zval(return_value, ra->hosts[i], z_tmp);
}
zval_dtor(&z_fun);
}
PHP_METHOD(RedisArray, setOption)
{
zval *object, z_fun, z_args[2];
int i;
RedisArray *ra;
zend_long opt;
char *val_str;
strlen_t val_len;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ols",
&object, redis_array_ce, &opt, &val_str, &val_len) == FAILURE) {
RETURN_FALSE;
}
if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
/* prepare call */
ZVAL_STRINGL(&z_fun, "setOption", 9);
/* copy args */
ZVAL_LONG(&z_args[0], opt);
ZVAL_STRINGL(&z_args[1], val_str, val_len);
array_init(return_value);
for(i = 0; i < ra->count; ++i) {
zval zv, *z_tmp = &zv;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_tmp);
#endif
/* Call each node in turn */
call_user_function(&redis_ce->function_table, &ra->redis[i], &z_fun, z_tmp, 2, z_args);
add_assoc_zval(return_value, ra->hosts[i], z_tmp);
}
zval_dtor(&z_args[1]);
zval_dtor(&z_fun);
}
PHP_METHOD(RedisArray, select)
{
zval *object, z_fun, z_args[1];
int i;
RedisArray *ra;
zend_long opt;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol",
&object, redis_array_ce, &opt) == FAILURE) {
RETURN_FALSE;
}
if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
/* prepare call */
ZVAL_STRINGL(&z_fun, "select", 6);
/* copy args */
ZVAL_LONG(&z_args[0], opt);
array_init(return_value);
for(i = 0; i < ra->count; ++i) {
zval zv, *z_tmp = &zv;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_tmp);
#endif
/* Call each node in turn */
call_user_function(&redis_ce->function_table, &ra->redis[i], &z_fun, z_tmp, 1, z_args);
add_assoc_zval(return_value, ra->hosts[i], z_tmp);
}
zval_dtor(&z_fun);
}
#if (PHP_MAJOR_VERSION < 7)
#define HANDLE_MULTI_EXEC(ra, cmd) do { \
if (ra && ra->z_multi_exec) { \
int i, num_varargs;\
zval ***varargs = NULL, *z_arg_array; \
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O*",\
&object, redis_array_ce, &varargs, &num_varargs) == FAILURE) {\
RETURN_FALSE;\
}\
/* copy all args into a zval hash table */\
MAKE_STD_ZVAL(z_arg_array); \
array_init(z_arg_array);\
for(i = 0; i < num_varargs; ++i) {\
zval *z_tmp;\
MAKE_STD_ZVAL(z_tmp); \
ZVAL_ZVAL(z_tmp, *varargs[i], 1, 0); \
add_next_index_zval(z_arg_array, z_tmp); \
}\
/* call */\
ra_forward_call(INTERNAL_FUNCTION_PARAM_PASSTHRU, ra, cmd, sizeof(cmd) - 1, z_arg_array, NULL); \
zval_ptr_dtor(&z_arg_array); \
if(varargs) {\
efree(varargs);\
}\
return;\
}\
}while(0)
#else
#define HANDLE_MULTI_EXEC(ra, cmd) do { \
if (ra && ra->z_multi_exec) { \
int i, num_varargs; \
zval *varargs = NULL, z_arg_array; \
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O*", \
&object, redis_array_ce, &varargs, &num_varargs) == FAILURE) { \
RETURN_FALSE;\
} \
/* copy all args into a zval hash table */\
array_init(&z_arg_array); \
for (i = 0; i < num_varargs; i++) { \
zval z_tmp; \
ZVAL_ZVAL(&z_tmp, &varargs[i], 1, 0); \
add_next_index_zval(&z_arg_array, &z_tmp); \
} \
/* call */\
ra_forward_call(INTERNAL_FUNCTION_PARAM_PASSTHRU, ra, cmd, sizeof(cmd) - 1, &z_arg_array, NULL); \
zval_dtor(&z_arg_array); \
return; \
} \
} while(0)
#endif
/* MGET will distribute the call to several nodes and regroup the values. */
PHP_METHOD(RedisArray, mget)
{
zval *object, *z_keys, z_argarray, *data, z_ret, *z_cur, z_tmp_array, *z_tmp;
int i, j, n;
RedisArray *ra;
int *pos, argc, *argc_each;
HashTable *h_keys;
zval **argv;
if ((ra = redis_array_get(getThis() TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
/* Multi/exec support */
HANDLE_MULTI_EXEC(ra, "MGET");
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa",
&object, redis_array_ce, &z_keys) == FAILURE) {
RETURN_FALSE;
}
/* init data structures */
h_keys = Z_ARRVAL_P(z_keys);
if ((argc = zend_hash_num_elements(h_keys)) == 0) {
RETURN_FALSE;
}
argv = emalloc(argc * sizeof(zval*));
pos = emalloc(argc * sizeof(int));
argc_each = emalloc(ra->count * sizeof(int));
memset(argc_each, 0, ra->count * sizeof(int));
/* associate each key to a redis node */
i = 0;
ZEND_HASH_FOREACH_VAL(h_keys, data) {
/* If we need to represent a long key as a string */
unsigned int key_len;
char kbuf[40], *key_lookup;
/* phpredis proper can only use string or long keys, so restrict to that here */
if (Z_TYPE_P(data) != IS_STRING && Z_TYPE_P(data) != IS_LONG) {
php_error_docref(NULL TSRMLS_CC, E_ERROR, "MGET: all keys must be strings or longs");
efree(argv);
efree(pos);
efree(argc_each);
RETURN_FALSE;
}
/* Convert to a string for hash lookup if it isn't one */
if (Z_TYPE_P(data) == IS_STRING) {
key_len = Z_STRLEN_P(data);
key_lookup = Z_STRVAL_P(data);
} else {
key_len = snprintf(kbuf, sizeof(kbuf), "%ld", Z_LVAL_P(data));
key_lookup = (char*)kbuf;
}
/* Find our node */
if (ra_find_node(ra, key_lookup, key_len, &pos[i] TSRMLS_CC) == NULL) {
/* TODO: handle */
}
argc_each[pos[i]]++; /* count number of keys per node */
argv[i++] = data;
} ZEND_HASH_FOREACH_END();
array_init(&z_tmp_array);
/* calls */
for(n = 0; n < ra->count; ++n) { /* for each node */
/* We don't even need to make a call to this node if no keys go there */
if(!argc_each[n]) continue;
/* copy args for MGET call on node. */
array_init(&z_argarray);
for(i = 0; i < argc; ++i) {
if(pos[i] != n) continue;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_tmp);
#else
zval zv;
z_tmp = &zv;
#endif
ZVAL_ZVAL(z_tmp, argv[i], 1, 0);
add_next_index_zval(&z_argarray, z_tmp);
}
zval z_fun;
/* prepare call */
ZVAL_STRINGL(&z_fun, "MGET", 4);
/* call MGET on the node */
call_user_function(&redis_ce->function_table, &ra->redis[n], &z_fun, &z_ret, 1, &z_argarray);
zval_dtor(&z_fun);
/* cleanup args array */
zval_dtor(&z_argarray);
/* Error out if we didn't get a proper response */
if (Z_TYPE(z_ret) != IS_ARRAY) {
/* cleanup */
zval_dtor(&z_ret);
zval_dtor(&z_tmp_array);
efree(argv);
efree(pos);
efree(argc_each);
/* failure */
RETURN_FALSE;
}
for(i = 0, j = 0; i < argc; ++i) {
if (pos[i] != n || (z_cur = zend_hash_index_find(Z_ARRVAL(z_ret), j++)) == NULL) continue;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_tmp);
#else
zval zv;
z_tmp = &zv;
#endif
ZVAL_ZVAL(z_tmp, z_cur, 1, 0);
add_index_zval(&z_tmp_array, i, z_tmp);
}
zval_dtor(&z_ret);
}
array_init(return_value);
/* copy temp array in the right order to return_value */
for(i = 0; i < argc; ++i) {
if ((z_cur = zend_hash_index_find(Z_ARRVAL(z_tmp_array), i)) == NULL) continue;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_tmp);
#else
zval zv;
z_tmp = &zv;
#endif
ZVAL_ZVAL(z_tmp, z_cur, 1, 0);
add_next_index_zval(return_value, z_tmp);
}
/* cleanup */
zval_dtor(&z_tmp_array);
efree(argv);
efree(pos);
efree(argc_each);
}
/* MSET will distribute the call to several nodes and regroup the values. */
PHP_METHOD(RedisArray, mset)
{
zval *object, *z_keys, z_argarray, *data, z_ret, **argv;
int i = 0, n;
RedisArray *ra;
int *pos, argc, *argc_each;
HashTable *h_keys;
char *key, **keys, kbuf[40];
int key_len, *key_lens;
zend_string *zkey;
ulong idx;
if ((ra = redis_array_get(getThis() TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
/* Multi/exec support */
HANDLE_MULTI_EXEC(ra, "MSET");
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa",
&object, redis_array_ce, &z_keys) == FAILURE)
{
RETURN_FALSE;
}
/* init data structures */
h_keys = Z_ARRVAL_P(z_keys);
if ((argc = zend_hash_num_elements(h_keys)) == 0) {
RETURN_FALSE;
}
argv = emalloc(argc * sizeof(zval*));
pos = emalloc(argc * sizeof(int));
keys = emalloc(argc * sizeof(char*));
key_lens = emalloc(argc * sizeof(int));
argc_each = emalloc(ra->count * sizeof(int));
memset(argc_each, 0, ra->count * sizeof(int));
/* associate each key to a redis node */
ZEND_HASH_FOREACH_KEY_VAL(h_keys, idx, zkey, data) {
/* If the key isn't a string, make a string representation of it */
if (zkey) {
key_len = ZSTR_LEN(zkey);
key = ZSTR_VAL(zkey);
} else {
key_len = snprintf(kbuf, sizeof(kbuf), "%lu", idx);
key = kbuf;
}
if (ra_find_node(ra, key, (int)key_len, &pos[i] TSRMLS_CC) == NULL) {
// TODO: handle
}
argc_each[pos[i]]++; /* count number of keys per node */
keys[i] = estrndup(key, key_len);
key_lens[i] = (int)key_len;
argv[i] = data;
i++;
} ZEND_HASH_FOREACH_END();
/* calls */
for (n = 0; n < ra->count; ++n) { /* for each node */
/* We don't even need to make a call to this node if no keys go there */
if(!argc_each[n]) continue;
int found = 0;
/* copy args */
array_init(&z_argarray);
for(i = 0; i < argc; ++i) {
if(pos[i] != n) continue;
zval zv, *z_tmp = &zv;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_tmp);
#endif
if (argv[i] == NULL) {
ZVAL_NULL(z_tmp);
} else {
ZVAL_ZVAL(z_tmp, argv[i], 1, 0);
}
add_assoc_zval_ex(&z_argarray, keys[i], key_lens[i], z_tmp);
found++;
}
if(!found) {
zval_dtor(&z_argarray);
continue; /* don't run empty MSETs */
}
if(ra->index) { /* add MULTI */
ra_index_multi(&ra->redis[n], MULTI TSRMLS_CC);
}
zval z_fun;
/* prepare call */
ZVAL_STRINGL(&z_fun, "MSET", 4);
/* call */
call_user_function(&redis_ce->function_table, &ra->redis[n], &z_fun, &z_ret, 1, &z_argarray);
zval_dtor(&z_fun);
zval_dtor(&z_ret);
if(ra->index) {
ra_index_keys(&z_argarray, &ra->redis[n] TSRMLS_CC); /* use SADD to add keys to node index */
ra_index_exec(&ra->redis[n], NULL, 0 TSRMLS_CC); /* run EXEC */
}
zval_dtor(&z_argarray);
}
/* Free any keys that we needed to allocate memory for, because they weren't strings */
for(i = 0; i < argc; i++) {
efree(keys[i]);
}
/* cleanup */
efree(keys);
efree(key_lens);
efree(argv);
efree(pos);
efree(argc_each);
RETURN_TRUE;
}
/* DEL will distribute the call to several nodes and regroup the values. */
PHP_METHOD(RedisArray, del)
{
zval *object, z_keys, z_fun, *data, z_ret, *z_tmp, *z_args;
int i, n;
RedisArray *ra;
int *pos, argc = ZEND_NUM_ARGS(), *argc_each;
HashTable *h_keys;
zval **argv;
long total = 0;
int free_zkeys = 0;
if ((ra = redis_array_get(getThis() TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
/* Multi/exec support */
HANDLE_MULTI_EXEC(ra, "DEL");
/* get all args in z_args */
z_args = emalloc(argc * sizeof(zval));
if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
efree(z_args);
RETURN_FALSE;
}
/* if single array arg, point z_keys to it. */
if (argc == 1 && Z_TYPE(z_args[0]) == IS_ARRAY) {
z_keys = z_args[0];
} else {
/* copy all elements to z_keys */
array_init(&z_keys);
for (i = 0; i < argc; ++i) {
zval *z_arg = &z_args[i];
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_tmp);
#else
zval zv;
z_tmp = &zv;
#endif
ZVAL_ZVAL(z_tmp, z_arg, 1, 0);
/* add copy to z_keys */
add_next_index_zval(&z_keys, z_tmp);
}
free_zkeys = 1;
}
/* init data structures */
h_keys = Z_ARRVAL(z_keys);
if ((argc = zend_hash_num_elements(h_keys)) == 0) {
if (free_zkeys) zval_dtor(&z_keys);
efree(z_args);
RETURN_FALSE;
}
argv = emalloc(argc * sizeof(zval*));
pos = emalloc(argc * sizeof(int));
argc_each = emalloc(ra->count * sizeof(int));
memset(argc_each, 0, ra->count * sizeof(int));
/* associate each key to a redis node */
i = 0;
ZEND_HASH_FOREACH_VAL(h_keys, data) {
if (Z_TYPE_P(data) != IS_STRING) {
php_error_docref(NULL TSRMLS_CC, E_ERROR, "DEL: all keys must be string.");
if (free_zkeys) zval_dtor(&z_keys);
efree(z_args);
efree(argv);
efree(pos);
efree(argc_each);
RETURN_FALSE;
}
if (ra_find_node(ra, Z_STRVAL_P(data), Z_STRLEN_P(data), &pos[i] TSRMLS_CC) == NULL) {
// TODO: handle
}
argc_each[pos[i]]++; /* count number of keys per node */
argv[i++] = data;
} ZEND_HASH_FOREACH_END();
/* prepare call */
ZVAL_STRINGL(&z_fun, "DEL", 3);
/* calls */
for(n = 0; n < ra->count; ++n) { /* for each node */
/* We don't even need to make a call to this node if no keys go there */
if(!argc_each[n]) continue;
int found = 0;
zval z_argarray;
/* copy args */
array_init(&z_argarray);
for(i = 0; i < argc; ++i) {
if(pos[i] != n) continue;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_tmp);
#else
zval zv;
z_tmp = &zv;
#endif
ZVAL_ZVAL(z_tmp, argv[i], 1, 0);
add_next_index_zval(&z_argarray, z_tmp);
found++;
}
if(!found) { /* don't run empty DELs */
zval_dtor(&z_argarray);
continue;
}
if(ra->index) { /* add MULTI */
ra_index_multi(&ra->redis[n], MULTI TSRMLS_CC);
}
/* call */
call_user_function(&redis_ce->function_table, &ra->redis[n], &z_fun, &z_ret, 1, &z_argarray);
if(ra->index) {
zval_dtor(&z_ret);
ra_index_del(&z_argarray, &ra->redis[n] TSRMLS_CC); /* use SREM to remove keys from node index */
ra_index_exec(&ra->redis[n], &z_ret, 0 TSRMLS_CC); /* run EXEC */
}
total += Z_LVAL(z_ret); /* increment total */
zval_dtor(&z_argarray);
zval_dtor(&z_ret);
}
/* cleanup */
zval_dtor(&z_fun);
efree(argv);
efree(pos);
efree(argc_each);
if(free_zkeys) {
zval_dtor(&z_keys);
}
efree(z_args);
RETURN_LONG(total);
}
PHP_METHOD(RedisArray, multi)
{
zval *object;
RedisArray *ra;
zval *z_redis;
char *host;
strlen_t host_len;
zend_long multi_value = MULTI;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os|l",
&object, redis_array_ce, &host, &host_len, &multi_value) == FAILURE) {
RETURN_FALSE;
}
if ((ra = redis_array_get(object TSRMLS_CC)) == NULL) {
RETURN_FALSE;
}
/* find node */
z_redis = ra_find_node_by_name(ra, host, host_len TSRMLS_CC);
if(!z_redis) {
RETURN_FALSE;
}
if(multi_value != MULTI && multi_value != PIPELINE) {
RETURN_FALSE;
}
/* save multi object */
ra->z_multi_exec = z_redis;
/* switch redis instance to multi/exec mode. */
ra_index_multi(z_redis, multi_value TSRMLS_CC);
/* return this. */
RETURN_ZVAL(object, 1, 0);
}
PHP_METHOD(RedisArray, exec)
{
zval *object;
RedisArray *ra;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O",
&object, redis_array_ce) == FAILURE) {
RETURN_FALSE;
}
if ((ra = redis_array_get(object TSRMLS_CC)) == NULL || !ra->z_multi_exec) {
RETURN_FALSE;
}
/* switch redis instance out of multi/exec mode. */
ra_index_exec(ra->z_multi_exec, return_value, 1 TSRMLS_CC);
/* remove multi object */
ra->z_multi_exec = NULL;
}
PHP_METHOD(RedisArray, discard)
{
zval *object;
RedisArray *ra;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O",
&object, redis_array_ce) == FAILURE) {
RETURN_FALSE;
}
if ((ra = redis_array_get(object TSRMLS_CC)) == NULL || !ra->z_multi_exec) {
RETURN_FALSE;
}
/* switch redis instance out of multi/exec mode. */
ra_index_discard(ra->z_multi_exec, return_value TSRMLS_CC);
/* remove multi object */
ra->z_multi_exec = NULL;
}
PHP_METHOD(RedisArray, unwatch)
{
zval *object;
RedisArray *ra;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O",
&object, redis_array_ce) == FAILURE) {
RETURN_FALSE;
}
if ((ra = redis_array_get(object TSRMLS_CC)) == NULL || !ra->z_multi_exec) {
RETURN_FALSE;
}
/* unwatch keys, stay in multi/exec mode. */
ra_index_unwatch(ra->z_multi_exec, return_value TSRMLS_CC);
}
redis-3.1.6/redis_array.h 0000644 0001750 0000012 00000003620 13223116600 016056 0 ustar pyatsukhnenko wheel #ifndef REDIS_ARRAY_H
#define REDIS_ARRAY_H
#ifdef PHP_WIN32
#include "win32/php_stdint.h"
#else
#include
#endif
#include "common.h"
PHP_METHOD(RedisArray, __construct);
PHP_METHOD(RedisArray, __call);
PHP_METHOD(RedisArray, _hosts);
PHP_METHOD(RedisArray, _target);
PHP_METHOD(RedisArray, _instance);
PHP_METHOD(RedisArray, _function);
PHP_METHOD(RedisArray, _distributor);
PHP_METHOD(RedisArray, _rehash);
PHP_METHOD(RedisArray, select);
PHP_METHOD(RedisArray, info);
PHP_METHOD(RedisArray, ping);
PHP_METHOD(RedisArray, flushdb);
PHP_METHOD(RedisArray, flushall);
PHP_METHOD(RedisArray, mget);
PHP_METHOD(RedisArray, mset);
PHP_METHOD(RedisArray, del);
PHP_METHOD(RedisArray, keys);
PHP_METHOD(RedisArray, getOption);
PHP_METHOD(RedisArray, setOption);
PHP_METHOD(RedisArray, save);
PHP_METHOD(RedisArray, bgsave);
PHP_METHOD(RedisArray, multi);
PHP_METHOD(RedisArray, exec);
PHP_METHOD(RedisArray, discard);
PHP_METHOD(RedisArray, unwatch);
typedef struct RedisArray_ {
int count;
char **hosts; /* array of host:port strings */
zval *redis; /* array of Redis instances */
zval *z_multi_exec; /* Redis instance to be used in multi-exec */
zend_bool index; /* use per-node index */
zend_bool auto_rehash; /* migrate keys on read operations */
zend_bool pconnect; /* should we use pconnect */
zval z_fun; /* key extractor, callable */
zval z_dist; /* key distributor, callable */
HashTable *pure_cmds; /* hash table */
double connect_timeout; /* socket connect timeout */
double read_timeout; /* socket read timeout */
struct RedisArray_ *prev;
} RedisArray;
#if (PHP_MAJOR_VERSION < 7)
zend_object_value create_redis_array_object(zend_class_entry *ce TSRMLS_DC);
void free_redis_array_object(void *object TSRMLS_DC);
#else
zend_object *create_redis_array_object(zend_class_entry *ce TSRMLS_DC);
void free_redis_array_object(zend_object *object);
#endif
#endif
redis-3.1.6/redis_array_impl.c 0000644 0001750 0000012 00000106676 13223116600 017111 0 ustar pyatsukhnenko wheel /*
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2009 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: Nicolas Favre-Felix |
| Maintainer: Michael Grunder |
+----------------------------------------------------------------------+
*/
#include "redis_array_impl.h"
#include "php_redis.h"
#include "library.h"
#include "php_variables.h"
#include "SAPI.h"
#include "ext/standard/url.h"
#include "ext/standard/crc32.h"
#define PHPREDIS_INDEX_NAME "__phpredis_array_index__"
extern zend_class_entry *redis_ce;
RedisArray*
ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC)
{
int i = 0, host_len;
char *host, *p;
short port;
zval *zpData, z_cons, z_ret;
redis_object *redis;
/* function calls on the Redis object */
ZVAL_STRINGL(&z_cons, "__construct", 11);
/* init connections */
ZEND_HASH_FOREACH_VAL(hosts, zpData) {
if (Z_TYPE_P(zpData) != IS_STRING) {
zval_dtor(&z_cons);
return NULL;
}
/* default values */
host = Z_STRVAL_P(zpData);
host_len = Z_STRLEN_P(zpData);
ra->hosts[i] = estrndup(host, host_len);
port = 6379;
if((p = strrchr(host, ':'))) { /* found port */
host_len = p - host;
port = (short)atoi(p+1);
} else if(strchr(host,'/') != NULL) { /* unix socket */
port = -1;
}
/* create Redis object */
#if (PHP_MAJOR_VERSION < 7)
INIT_PZVAL(&ra->redis[i]);
#endif
object_init_ex(&ra->redis[i], redis_ce);
call_user_function(&redis_ce->function_table, &ra->redis[i], &z_cons, &z_ret, 0, NULL);
zval_dtor(&z_ret);
#if (PHP_MAJOR_VERSION < 7)
redis = (redis_object *)zend_objects_get_address(&ra->redis[i] TSRMLS_CC);
#else
redis = (redis_object *)((char *)Z_OBJ_P(&ra->redis[i]) - XtOffsetOf(redis_object, std));
#endif
/* create socket */
redis->sock = redis_sock_create(host, host_len, port, ra->connect_timeout, ra->read_timeout, ra->pconnect, NULL, retry_interval, b_lazy_connect);
if (!b_lazy_connect)
{
/* connect */
redis_sock_server_open(redis->sock TSRMLS_CC);
}
ra->count = ++i;
} ZEND_HASH_FOREACH_END();
zval_dtor(&z_cons);
return ra;
}
/* List pure functions */
void
ra_init_function_table(RedisArray *ra)
{
ALLOC_HASHTABLE(ra->pure_cmds);
zend_hash_init(ra->pure_cmds, 0, NULL, NULL, 0);
zend_hash_str_update_ptr(ra->pure_cmds, "EXISTS", sizeof("EXISTS") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "GET", sizeof("GET") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "GETBIT", sizeof("GETBIT") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "GETRANGE", sizeof("GETRANGE") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "HEXISTS", sizeof("HEXISTS") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "HGET", sizeof("HGET") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "HGETALL", sizeof("HGETALL") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "HKEYS", sizeof("HKEYS") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "HLEN", sizeof("HLEN") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "HMGET", sizeof("HMGET") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "HVALS", sizeof("HVALS") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "LINDEX", sizeof("LINDEX") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "LLEN", sizeof("LLEN") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "LRANGE", sizeof("LRANGE") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "OBJECT", sizeof("OBJECT") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "SCARD", sizeof("SCARD") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "SDIFF", sizeof("SDIFF") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "SINTER", sizeof("SINTER") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "SISMEMBER", sizeof("SISMEMBER") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "SMEMBERS", sizeof("SMEMBERS") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "SRANDMEMBER", sizeof("SRANDMEMBER") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "STRLEN", sizeof("STRLEN") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "SUNION", sizeof("SUNION") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "TYPE", sizeof("TYPE") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "ZCARD", sizeof("ZCARD") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "ZCOUNT", sizeof("ZCOUNT") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "ZRANGE", sizeof("ZRANGE") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "ZRANK", sizeof("ZRANK") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "ZREVRANGE", sizeof("ZREVRANGE") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "ZREVRANGEBYSCORE", sizeof("ZREVRANGEBYSCORE") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "ZREVRANK", sizeof("ZREVRANK") - 1, NULL);
zend_hash_str_update_ptr(ra->pure_cmds, "ZSCORE", sizeof("ZSCORE") - 1, NULL);
}
static int
ra_find_name(const char *name) {
const char *ini_names, *p, *next;
/* php_printf("Loading redis array with name=[%s]\n", name); */
ini_names = INI_STR("redis.arrays.names");
for(p = ini_names; p;) {
next = strchr(p, ',');
if(next) {
if(strncmp(p, name, next - p) == 0) {
return 1;
}
} else {
if(strcmp(p, name) == 0) {
return 1;
}
break;
}
p = next + 1;
}
return 0;
}
/* laod array from INI settings */
RedisArray *ra_load_array(const char *name TSRMLS_DC) {
zval *z_data, z_fun, z_dist;
zval z_params_hosts;
zval z_params_prev;
zval z_params_funs;
zval z_params_dist;
zval z_params_index;
zval z_params_autorehash;
zval z_params_retry_interval;
zval z_params_pconnect;
zval z_params_connect_timeout;
zval z_params_read_timeout;
zval z_params_lazy_connect;
RedisArray *ra = NULL;
zend_bool b_index = 0, b_autorehash = 0, b_pconnect = 0;
long l_retry_interval = 0;
zend_bool b_lazy_connect = 0;
double d_connect_timeout = 0, read_timeout = 0.0;
HashTable *hHosts = NULL, *hPrev = NULL;
size_t name_len = strlen(name);
char *iptr;
/* find entry */
if(!ra_find_name(name))
return ra;
/* find hosts */
array_init(&z_params_hosts);
if ((iptr = INI_STR("redis.arrays.hosts")) != NULL) {
sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_hosts TSRMLS_CC);
}
if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_hosts), name, name_len)) != NULL) {
hHosts = Z_ARRVAL_P(z_data);
}
/* find previous hosts */
array_init(&z_params_prev);
if ((iptr = INI_STR("redis.arrays.previous")) != NULL) {
sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_prev TSRMLS_CC);
}
if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_prev), name, name_len)) != NULL) {
hPrev = Z_ARRVAL_P(z_data);
}
/* find function */
array_init(&z_params_funs);
if ((iptr = INI_STR("redis.arrays.functions")) != NULL) {
sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_funs TSRMLS_CC);
}
ZVAL_NULL(&z_fun);
if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_funs), name, name_len)) != NULL) {
ZVAL_ZVAL(&z_fun, z_data, 1, 0);
}
/* find distributor */
array_init(&z_params_dist);
if ((iptr = INI_STR("redis.arrays.distributor")) != NULL) {
sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_dist TSRMLS_CC);
}
ZVAL_NULL(&z_dist);
if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_dist), name, name_len)) != NULL) {
ZVAL_ZVAL(&z_dist, z_data, 1, 0);
}
/* find index option */
array_init(&z_params_index);
if ((iptr = INI_STR("redis.arrays.index")) != NULL) {
sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_index TSRMLS_CC);
}
if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_index), name, name_len)) != NULL) {
if (Z_TYPE_P(z_data) == IS_STRING && strncmp(Z_STRVAL_P(z_data), "1", 1) == 0) {
b_index = 1;
}
}
/* find autorehash option */
array_init(&z_params_autorehash);
if ((iptr = INI_STR("redis.arrays.autorehash")) != NULL) {
sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_autorehash TSRMLS_CC);
}
if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_autorehash), name, name_len)) != NULL) {
if(Z_TYPE_P(z_data) == IS_STRING && strncmp(Z_STRVAL_P(z_data), "1", 1) == 0) {
b_autorehash = 1;
}
}
/* find retry interval option */
array_init(&z_params_retry_interval);
if ((iptr = INI_STR("redis.arrays.retryinterval")) != NULL) {
sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_retry_interval TSRMLS_CC);
}
if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_retry_interval), name, name_len)) != NULL) {
if (Z_TYPE_P(z_data) == IS_LONG) {
l_retry_interval = Z_LVAL_P(z_data);
} else if (Z_TYPE_P(z_data) == IS_STRING) {
l_retry_interval = atol(Z_STRVAL_P(z_data));
}
}
/* find pconnect option */
array_init(&z_params_pconnect);
if ((iptr = INI_STR("redis.arrays.pconnect")) != NULL) {
sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_pconnect TSRMLS_CC);
}
if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_pconnect), name, name_len)) != NULL) {
if(Z_TYPE_P(z_data) == IS_STRING && strncmp(Z_STRVAL_P(z_data), "1", 1) == 0) {
b_pconnect = 1;
}
}
/* find lazy connect option */
array_init(&z_params_lazy_connect);
if ((iptr = INI_STR("redis.arrays.lazyconnect")) != NULL) {
sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_lazy_connect TSRMLS_CC);
}
if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_lazy_connect), name, name_len)) != NULL) {
if (Z_TYPE_P(z_data) == IS_STRING && strncmp(Z_STRVAL_P(z_data), "1", 1) == 0) {
b_lazy_connect = 1;
}
}
/* find connect timeout option */
array_init(&z_params_connect_timeout);
if ((iptr = INI_STR("redis.arrays.connecttimeout")) != NULL) {
sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_connect_timeout TSRMLS_CC);
}
if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_connect_timeout), name, name_len)) != NULL) {
if (Z_TYPE_P(z_data) == IS_DOUBLE) {
d_connect_timeout = Z_DVAL_P(z_data);
} else if (Z_TYPE_P(z_data) == IS_STRING) {
d_connect_timeout = atof(Z_STRVAL_P(z_data));
} else if (Z_TYPE_P(z_data) == IS_LONG) {
d_connect_timeout = Z_LVAL_P(z_data);
}
}
/* find read timeout option */
array_init(&z_params_read_timeout);
if ((iptr = INI_STR("redis.arrays.readtimeout")) != NULL) {
sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_params_read_timeout TSRMLS_CC);
}
if ((z_data = zend_hash_str_find(Z_ARRVAL(z_params_read_timeout), name, name_len)) != NULL) {
if (Z_TYPE_P(z_data) == IS_DOUBLE) {
read_timeout = Z_DVAL_P(z_data);
} else if (Z_TYPE_P(z_data) == IS_STRING) {
read_timeout = atof(Z_STRVAL_P(z_data));
} else if (Z_TYPE_P(z_data) == IS_LONG) {
read_timeout = Z_LVAL_P(z_data);
}
}
/* create RedisArray object */
ra = ra_make_array(hHosts, &z_fun, &z_dist, hPrev, b_index, b_pconnect, l_retry_interval, b_lazy_connect, d_connect_timeout, read_timeout TSRMLS_CC);
if (ra) {
ra->auto_rehash = b_autorehash;
if(ra->prev) ra->prev->auto_rehash = b_autorehash;
}
/* cleanup */
zval_dtor(&z_params_hosts);
zval_dtor(&z_params_prev);
zval_dtor(&z_params_funs);
zval_dtor(&z_params_dist);
zval_dtor(&z_params_index);
zval_dtor(&z_params_autorehash);
zval_dtor(&z_params_retry_interval);
zval_dtor(&z_params_pconnect);
zval_dtor(&z_params_connect_timeout);
zval_dtor(&z_params_read_timeout);
zval_dtor(&z_params_lazy_connect);
zval_dtor(&z_dist);
zval_dtor(&z_fun);
return ra;
}
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, double read_timeout TSRMLS_DC) {
int i, count;
RedisArray *ra;
if (!hosts || (count = zend_hash_num_elements(hosts)) == 0) return NULL;
/* create object */
ra = emalloc(sizeof(RedisArray));
ra->hosts = ecalloc(count, sizeof(char *));
ra->redis = ecalloc(count, sizeof(zval));
ra->count = 0;
ra->z_multi_exec = NULL;
ra->index = b_index;
ra->auto_rehash = 0;
ra->pconnect = b_pconnect;
ra->connect_timeout = connect_timeout;
ra->read_timeout = read_timeout;
if (ra_load_hosts(ra, hosts, retry_interval, b_lazy_connect TSRMLS_CC) == NULL || !ra->count) {
for (i = 0; i < ra->count; ++i) {
zval_dtor(&ra->redis[i]);
efree(ra->hosts[i]);
}
efree(ra->redis);
efree(ra->hosts);
efree(ra);
return NULL;
}
ra->prev = hosts_prev ? ra_make_array(hosts_prev, z_fun, z_dist, NULL, b_index, b_pconnect, retry_interval, b_lazy_connect, connect_timeout, read_timeout TSRMLS_CC) : NULL;
/* init array data structures */
ra_init_function_table(ra);
/* Set hash function and distribtor if provided */
ZVAL_ZVAL(&ra->z_fun, z_fun, 1, 0);
ZVAL_ZVAL(&ra->z_dist, z_dist, 1, 0);
return ra;
}
/* call userland key extraction function */
char *
ra_call_extractor(RedisArray *ra, const char *key, int key_len, int *out_len TSRMLS_DC)
{
char *out = NULL;
zval z_ret, z_argv;
/* check that we can call the extractor function */
#if (PHP_MAJOR_VERSION < 7)
if (!zend_is_callable_ex(&ra->z_fun, NULL, 0, NULL, NULL, NULL, NULL TSRMLS_CC)) {
#else
if (!zend_is_callable_ex(&ra->z_fun, NULL, 0, NULL, NULL, NULL)) {
#endif
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not call extractor function");
return NULL;
}
ZVAL_NULL(&z_ret);
/* call extraction function */
ZVAL_STRINGL(&z_argv, key, key_len);
call_user_function(EG(function_table), NULL, &ra->z_fun, &z_ret, 1, &z_argv);
if (Z_TYPE(z_ret) == IS_STRING) {
*out_len = Z_STRLEN(z_ret);
out = estrndup(Z_STRVAL(z_ret), *out_len);
}
zval_dtor(&z_argv);
zval_dtor(&z_ret);
return out;
}
static char *
ra_extract_key(RedisArray *ra, const char *key, int key_len, int *out_len TSRMLS_DC) {
char *start, *end;
*out_len = key_len;
if (Z_TYPE(ra->z_fun) != IS_NULL) {
return ra_call_extractor(ra, key, key_len, out_len TSRMLS_CC);
} else if ((start = strchr(key, '{')) == NULL || (end = strchr(start + 1, '}')) == NULL) {
return estrndup(key, key_len);
}
/* found substring */
*out_len = end - start - 1;
return estrndup(start + 1, *out_len);
}
/* call userland key distributor function */
int
ra_call_distributor(RedisArray *ra, const char *key, int key_len TSRMLS_DC)
{
int ret;
zval z_ret, z_argv;
/* check that we can call the extractor function */
#if (PHP_MAJOR_VERSION < 7)
if (!zend_is_callable_ex(&ra->z_dist, NULL, 0, NULL, NULL, NULL, NULL TSRMLS_CC)) {
#else
if (!zend_is_callable_ex(&ra->z_dist, NULL, 0, NULL, NULL, NULL)) {
#endif
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Could not call distributor function");
return -1;
}
ZVAL_NULL(&z_ret);
/* call extraction function */
ZVAL_STRINGL(&z_argv, key, key_len);
call_user_function(EG(function_table), NULL, &ra->z_dist, &z_ret, 1, &z_argv);
ret = (Z_TYPE(z_ret) == IS_LONG) ? Z_LVAL(z_ret) : -1;
zval_dtor(&z_argv);
zval_dtor(&z_ret);
return ret;
}
zval *
ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos TSRMLS_DC) {
uint32_t hash;
char *out;
int pos, out_len;
/* extract relevant part of the key */
out = ra_extract_key(ra, key, key_len, &out_len TSRMLS_CC);
if(!out) return NULL;
if (Z_TYPE(ra->z_dist) != IS_NULL) {
pos = ra_call_distributor(ra, key, key_len TSRMLS_CC);
if (pos < 0 || pos >= ra->count) {
efree(out);
return NULL;
}
} else {
uint64_t h64;
unsigned long ret = 0xffffffff;
size_t i;
/* hash */
for (i = 0; i < out_len; ++i) {
CRC32(ret, (unsigned char)out[i]);
}
hash = (ret ^ 0xffffffff);
/* get position on ring */
h64 = hash;
h64 *= ra->count;
h64 /= 0xffffffff;
pos = (int)h64;
}
efree(out);
if(out_pos) *out_pos = pos;
return &ra->redis[pos];
}
zval *
ra_find_node_by_name(RedisArray *ra, const char *host, int host_len TSRMLS_DC) {
int i;
for(i = 0; i < ra->count; ++i) {
if(strncmp(ra->hosts[i], host, host_len) == 0) {
return &ra->redis[i];
}
}
return NULL;
}
void
ra_index_multi(zval *z_redis, long multi_value TSRMLS_DC) {
zval z_fun_multi, z_ret;
zval z_args[1];
/* run MULTI */
ZVAL_STRINGL(&z_fun_multi, "MULTI", 5);
ZVAL_LONG(&z_args[0], multi_value);
call_user_function(&redis_ce->function_table, z_redis, &z_fun_multi, &z_ret, 1, z_args);
zval_dtor(&z_fun_multi);
zval_dtor(&z_ret);
}
static void
ra_index_change_keys(const char *cmd, zval *z_keys, zval *z_redis TSRMLS_DC) {
int i, argc;
zval z_fun, z_ret, *z_args;
/* alloc */
argc = 1 + zend_hash_num_elements(Z_ARRVAL_P(z_keys));
z_args = ecalloc(argc, sizeof(zval));
/* prepare first parameters */
ZVAL_STRING(&z_fun, cmd);
ZVAL_STRINGL(&z_args[0], PHPREDIS_INDEX_NAME, sizeof(PHPREDIS_INDEX_NAME) - 1);
/* prepare keys */
for(i = 0; i < argc - 1; ++i) {
zval *zv = zend_hash_index_find(Z_ARRVAL_P(z_keys), i);
if (zv == NULL) {
ZVAL_NULL(&z_args[i+1]);
} else {
z_args[i+1] = *zv;
}
}
/* run cmd */
call_user_function(&redis_ce->function_table, z_redis, &z_fun, &z_ret, argc, z_args);
zval_dtor(&z_args[0]);
zval_dtor(&z_fun);
zval_dtor(&z_ret);
efree(z_args); /* free container */
}
void
ra_index_del(zval *z_keys, zval *z_redis TSRMLS_DC) {
ra_index_change_keys("SREM", z_keys, z_redis TSRMLS_CC);
}
void
ra_index_keys(zval *z_pairs, zval *z_redis TSRMLS_DC) {
zval z_keys, *z_val;
zend_string *zkey;
ulong idx;
/* Initialize key array */
#if PHP_VERSION_ID > 50300
array_init_size(&z_keys, zend_hash_num_elements(Z_ARRVAL_P(z_pairs)));
#else
array_init(&z_keys);
#endif
/* Go through input array and add values to the key array */
ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(z_pairs), idx, zkey, z_val) {
zval zv, *z_new = &zv;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_new);
#endif
PHPREDIS_NOTUSED(z_val);
if (zkey) {
ZVAL_STRINGL(z_new, ZSTR_VAL(zkey), ZSTR_LEN(zkey));
} else {
ZVAL_LONG(z_new, idx);
}
zend_hash_next_index_insert(Z_ARRVAL(z_keys), z_new);
} ZEND_HASH_FOREACH_END();
/* add keys to index */
ra_index_change_keys("SADD", &z_keys, z_redis TSRMLS_CC);
/* cleanup */
zval_dtor(&z_keys);
}
void
ra_index_key(const char *key, int key_len, zval *z_redis TSRMLS_DC) {
zval z_fun_sadd, z_ret, z_args[2];
/* prepare args */
ZVAL_STRINGL(&z_fun_sadd, "SADD", 4);
ZVAL_STRINGL(&z_args[0], PHPREDIS_INDEX_NAME, sizeof(PHPREDIS_INDEX_NAME) - 1);
ZVAL_STRINGL(&z_args[1], key, key_len);
/* run SADD */
call_user_function(&redis_ce->function_table, z_redis, &z_fun_sadd, &z_ret, 2, z_args);
zval_dtor(&z_fun_sadd);
zval_dtor(&z_args[1]);
zval_dtor(&z_args[0]);
zval_dtor(&z_ret);
}
void
ra_index_exec(zval *z_redis, zval *return_value, int keep_all TSRMLS_DC) {
zval z_fun_exec, z_ret, *zp_tmp;
/* run EXEC */
ZVAL_STRINGL(&z_fun_exec, "EXEC", 4);
call_user_function(&redis_ce->function_table, z_redis, &z_fun_exec, &z_ret, 0, NULL);
zval_dtor(&z_fun_exec);
/* extract first element of exec array and put into return_value. */
if(Z_TYPE(z_ret) == IS_ARRAY) {
if(return_value) {
if(keep_all) {
zp_tmp = &z_ret;
RETVAL_ZVAL(zp_tmp, 1, 0);
} else if ((zp_tmp = zend_hash_index_find(Z_ARRVAL(z_ret), 0)) != NULL) {
RETVAL_ZVAL(zp_tmp, 1, 0);
}
}
}
zval_dtor(&z_ret);
/* zval *zptr = &z_ret; */
/* php_var_dump(&zptr, 0 TSRMLS_CC); */
}
void
ra_index_discard(zval *z_redis, zval *return_value TSRMLS_DC) {
zval z_fun_discard, z_ret;
/* run DISCARD */
ZVAL_STRINGL(&z_fun_discard, "DISCARD", 7);
call_user_function(&redis_ce->function_table, z_redis, &z_fun_discard, &z_ret, 0, NULL);
zval_dtor(&z_fun_discard);
zval_dtor(&z_ret);
}
void
ra_index_unwatch(zval *z_redis, zval *return_value TSRMLS_DC) {
zval z_fun_unwatch, z_ret;
/* run UNWATCH */
ZVAL_STRINGL(&z_fun_unwatch, "UNWATCH", 7);
call_user_function(&redis_ce->function_table, z_redis, &z_fun_unwatch, &z_ret, 0, NULL);
zval_dtor(&z_fun_unwatch);
zval_dtor(&z_ret);
}
zend_bool
ra_is_write_cmd(RedisArray *ra, const char *cmd, int cmd_len) {
zend_bool ret;
int i;
char *cmd_up = emalloc(1 + cmd_len);
/* convert to uppercase */
for(i = 0; i < cmd_len; ++i)
cmd_up[i] = toupper(cmd[i]);
cmd_up[cmd_len] = 0;
ret = zend_hash_str_exists(ra->pure_cmds, cmd_up, cmd_len);
efree(cmd_up);
return !ret;
}
/* run TYPE to find the type */
static zend_bool
ra_get_key_type(zval *z_redis, const char *key, int key_len, zval *z_from, long *res TSRMLS_DC) {
int i = 0;
zval z_fun, z_ret, z_arg, *z_data;
long success = 1;
/* Pipelined */
ra_index_multi(z_from, PIPELINE TSRMLS_CC);
/* prepare args */
ZVAL_STRINGL(&z_arg, key, key_len);
/* run TYPE */
ZVAL_NULL(&z_ret);
ZVAL_STRINGL(&z_fun, "TYPE", 4);
call_user_function(&redis_ce->function_table, z_redis, &z_fun, &z_ret, 1, &z_arg);
zval_dtor(&z_fun);
zval_dtor(&z_ret);
/* run TYPE */
ZVAL_NULL(&z_ret);
ZVAL_STRINGL(&z_fun, "TTL", 3);
call_user_function(&redis_ce->function_table, z_redis, &z_fun, &z_ret, 1, &z_arg);
zval_dtor(&z_fun);
zval_dtor(&z_ret);
/* Get the result from the pipeline. */
ra_index_exec(z_from, &z_ret, 1 TSRMLS_CC);
if (Z_TYPE(z_ret) == IS_ARRAY) {
ZEND_HASH_FOREACH_VAL(Z_ARRVAL(z_ret), z_data) {
if (z_data == NULL || Z_TYPE_P(z_data) != IS_LONG) {
success = 0;
break;
}
/* Get the result - Might change in the future to handle doubles as well */
res[i++] = Z_LVAL_P(z_data);
} ZEND_HASH_FOREACH_END();
}
zval_dtor(&z_arg);
zval_dtor(&z_ret);
return success;
}
/* delete key from source server index during rehashing */
static void
ra_remove_from_index(zval *z_redis, const char *key, int key_len TSRMLS_DC) {
zval z_fun_srem, z_ret, z_args[2];
/* run SREM on source index */
ZVAL_STRINGL(&z_fun_srem, "SREM", 4);
ZVAL_STRINGL(&z_args[0], PHPREDIS_INDEX_NAME, sizeof(PHPREDIS_INDEX_NAME) - 1);
ZVAL_STRINGL(&z_args[1], key, key_len);
call_user_function(&redis_ce->function_table, z_redis, &z_fun_srem, &z_ret, 2, z_args);
/* cleanup */
zval_dtor(&z_fun_srem);
zval_dtor(&z_args[1]);
zval_dtor(&z_args[0]);
zval_dtor(&z_ret);
}
/* delete key from source server during rehashing */
static zend_bool
ra_del_key(const char *key, int key_len, zval *z_from TSRMLS_DC) {
zval z_fun_del, z_ret, z_args[1];
/* in a transaction */
ra_index_multi(z_from, MULTI TSRMLS_CC);
/* run DEL on source */
ZVAL_STRINGL(&z_fun_del, "DEL", 3);
ZVAL_STRINGL(&z_args[0], key, key_len);
call_user_function(&redis_ce->function_table, z_from, &z_fun_del, &z_ret, 1, z_args);
zval_dtor(&z_fun_del);
zval_dtor(&z_args[0]);
zval_dtor(&z_ret);
/* remove key from index */
ra_remove_from_index(z_from, key, key_len TSRMLS_CC);
/* close transaction */
ra_index_exec(z_from, NULL, 0 TSRMLS_CC);
return 1;
}
static zend_bool
ra_expire_key(const char *key, int key_len, zval *z_to, long ttl TSRMLS_DC) {
zval z_fun_expire, z_ret, z_args[2];
if (ttl > 0)
{
/* run EXPIRE on target */
ZVAL_STRINGL(&z_fun_expire, "EXPIRE", 6);
ZVAL_STRINGL(&z_args[0], key, key_len);
ZVAL_LONG(&z_args[1], ttl);
call_user_function(&redis_ce->function_table, z_to, &z_fun_expire, &z_ret, 2, z_args);
zval_dtor(&z_fun_expire);
zval_dtor(&z_args[0]);
zval_dtor(&z_ret);
}
return 1;
}
static zend_bool
ra_move_zset(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) {
zval z_fun_zrange, z_fun_zadd, z_ret, z_ret_dest, z_args[4], *z_zadd_args, *z_score_p;
int i, count;
HashTable *h_zset_vals;
zend_string *zkey;
ulong idx;
/* run ZRANGE key 0 -1 WITHSCORES on source */
ZVAL_STRINGL(&z_fun_zrange, "ZRANGE", 6);
ZVAL_STRINGL(&z_args[0], key, key_len);
ZVAL_STRINGL(&z_args[1], "0", 1);
ZVAL_STRINGL(&z_args[2], "-1", 2);
ZVAL_BOOL(&z_args[3], 1);
call_user_function(&redis_ce->function_table, z_from, &z_fun_zrange, &z_ret, 4, z_args);
zval_dtor(&z_fun_zrange);
zval_dtor(&z_args[2]);
zval_dtor(&z_args[1]);
zval_dtor(&z_args[0]);
if(Z_TYPE(z_ret) != IS_ARRAY) { /* key not found or replaced */
/* TODO: report? */
zval_dtor(&z_ret);
return 0;
}
/* we now have an array of value → score pairs in z_ret. */
h_zset_vals = Z_ARRVAL(z_ret);
/* allocate argument array for ZADD */
count = zend_hash_num_elements(h_zset_vals);
z_zadd_args = ecalloc((1 + 2*count), sizeof(zval));
ZVAL_STRINGL(&z_zadd_args[0], key, key_len);
i = 1;
ZEND_HASH_FOREACH_KEY_VAL(h_zset_vals, idx, zkey, z_score_p) {
/* add score */
ZVAL_DOUBLE(&z_zadd_args[i], Z_DVAL_P(z_score_p));
/* add value */
if (zkey) {
ZVAL_STRINGL(&z_zadd_args[i+1], ZSTR_VAL(zkey), ZSTR_LEN(zkey));
} else {
ZVAL_LONG(&z_zadd_args[i+1], (long)idx);
}
i += 2;
} ZEND_HASH_FOREACH_END();
/* run ZADD on target */
ZVAL_STRINGL(&z_fun_zadd, "ZADD", 4);
call_user_function(&redis_ce->function_table, z_to, &z_fun_zadd, &z_ret_dest, 1 + 2 * count, z_zadd_args);
/* Expire if needed */
ra_expire_key(key, key_len, z_to, ttl TSRMLS_CC);
/* cleanup */
zval_dtor(&z_fun_zadd);
zval_dtor(&z_ret_dest);
zval_dtor(&z_ret);
/* Free the array itself */
for (i = 0; i < 1 + 2 * count; i++) {
zval_dtor(&z_zadd_args[i]);
}
efree(z_zadd_args);
return 1;
}
static zend_bool
ra_move_string(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) {
zval z_fun_get, z_fun_set, z_ret, z_args[3];
/* run GET on source */
ZVAL_STRINGL(&z_fun_get, "GET", 3);
ZVAL_STRINGL(&z_args[0], key, key_len);
call_user_function(&redis_ce->function_table, z_from, &z_fun_get, &z_ret, 1, z_args);
zval_dtor(&z_fun_get);
if(Z_TYPE(z_ret) != IS_STRING) { /* key not found or replaced */
/* TODO: report? */
zval_dtor(&z_args[0]);
zval_dtor(&z_ret);
return 0;
}
/* run SET on target */
if (ttl > 0) {
ZVAL_STRINGL(&z_fun_set, "SETEX", 5);
ZVAL_LONG(&z_args[1], ttl);
ZVAL_STRINGL(&z_args[2], Z_STRVAL(z_ret), Z_STRLEN(z_ret)); /* copy z_ret to arg 1 */
zval_dtor(&z_ret); /* free memory from our previous call */
call_user_function(&redis_ce->function_table, z_to, &z_fun_set, &z_ret, 3, z_args);
/* cleanup */
zval_dtor(&z_args[2]);
} else {
ZVAL_STRINGL(&z_fun_set, "SET", 3);
ZVAL_STRINGL(&z_args[1], Z_STRVAL(z_ret), Z_STRLEN(z_ret)); /* copy z_ret to arg 1 */
zval_dtor(&z_ret); /* free memory from our previous return value */
call_user_function(&redis_ce->function_table, z_to, &z_fun_set, &z_ret, 2, z_args);
/* cleanup */
zval_dtor(&z_args[1]);
}
zval_dtor(&z_fun_set);
zval_dtor(&z_args[0]);
zval_dtor(&z_ret);
return 1;
}
static zend_bool
ra_move_hash(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) {
zval z_fun_hgetall, z_fun_hmset, z_ret_dest, z_args[2];
/* run HGETALL on source */
ZVAL_STRINGL(&z_args[0], key, key_len);
ZVAL_STRINGL(&z_fun_hgetall, "HGETALL", 7);
call_user_function(&redis_ce->function_table, z_from, &z_fun_hgetall, &z_args[1], 1, z_args);
zval_dtor(&z_fun_hgetall);
if (Z_TYPE(z_args[1]) != IS_ARRAY) { /* key not found or replaced */
/* TODO: report? */
zval_dtor(&z_args[1]);
zval_dtor(&z_args[0]);
return 0;
}
/* run HMSET on target */
ZVAL_STRINGL(&z_fun_hmset, "HMSET", 5);
call_user_function(&redis_ce->function_table, z_to, &z_fun_hmset, &z_ret_dest, 2, z_args);
zval_dtor(&z_fun_hmset);
zval_dtor(&z_ret_dest);
/* Expire if needed */
ra_expire_key(key, key_len, z_to, ttl TSRMLS_CC);
/* cleanup */
zval_dtor(&z_args[1]);
zval_dtor(&z_args[0]);
return 1;
}
static zend_bool
ra_move_collection(const char *key, int key_len, zval *z_from, zval *z_to,
int list_count, const char **cmd_list,
int add_count, const char **cmd_add, long ttl TSRMLS_DC) {
zval z_fun_retrieve, z_fun_sadd, z_ret, *z_retrieve_args, *z_sadd_args, *z_data_p;
int count, i;
HashTable *h_set_vals;
/* run retrieval command on source */
ZVAL_STRING(&z_fun_retrieve, cmd_list[0]); /* set the command */
z_retrieve_args = ecalloc(list_count, sizeof(zval));
/* set the key */
ZVAL_STRINGL(&z_retrieve_args[0], key, key_len);
/* possibly add some other args if they were provided. */
for(i = 1; i < list_count; ++i) {
ZVAL_STRING(&z_retrieve_args[i], cmd_list[i]);
}
call_user_function(&redis_ce->function_table, z_from, &z_fun_retrieve, &z_ret, list_count, z_retrieve_args);
/* cleanup */
zval_dtor(&z_fun_retrieve);
for(i = 0; i < list_count; ++i) {
zval_dtor(&z_retrieve_args[i]);
}
efree(z_retrieve_args);
if(Z_TYPE(z_ret) != IS_ARRAY) { /* key not found or replaced */
/* TODO: report? */
zval_dtor(&z_ret);
return 0;
}
/* run SADD/RPUSH on target */
h_set_vals = Z_ARRVAL(z_ret);
count = 1 + zend_hash_num_elements(h_set_vals);
ZVAL_STRING(&z_fun_sadd, cmd_add[0]);
z_sadd_args = ecalloc(count, sizeof(zval));
ZVAL_STRINGL(&z_sadd_args[0], key, key_len);
i = 1;
ZEND_HASH_FOREACH_VAL(h_set_vals, z_data_p) {
/* add set elements */
ZVAL_ZVAL(&z_sadd_args[i], z_data_p, 1, 0);
i++;
} ZEND_HASH_FOREACH_END();
/* Clean up our input return value */
zval_dtor(&z_ret);
call_user_function(&redis_ce->function_table, z_to, &z_fun_sadd, &z_ret, count, z_sadd_args);
/* cleanup */
zval_dtor(&z_fun_sadd);
for (i = 0; i < count; i++) {
zval_dtor(&z_sadd_args[i]);
}
efree(z_sadd_args);
/* Clean up our output return value */
zval_dtor(&z_ret);
/* Expire if needed */
ra_expire_key(key, key_len, z_to, ttl TSRMLS_CC);
return 1;
}
static zend_bool
ra_move_set(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) {
const char *cmd_list[] = {"SMEMBERS"};
const char *cmd_add[] = {"SADD"};
return ra_move_collection(key, key_len, z_from, z_to, 1, cmd_list, 1, cmd_add, ttl TSRMLS_CC);
}
static zend_bool
ra_move_list(const char *key, int key_len, zval *z_from, zval *z_to, long ttl TSRMLS_DC) {
const char *cmd_list[] = {"LRANGE", "0", "-1"};
const char *cmd_add[] = {"RPUSH"};
return ra_move_collection(key, key_len, z_from, z_to, 3, cmd_list, 1, cmd_add, ttl TSRMLS_CC);
}
void
ra_move_key(const char *key, int key_len, zval *z_from, zval *z_to TSRMLS_DC) {
long res[2] = {0}, type, ttl;
zend_bool success = 0;
if (ra_get_key_type(z_from, key, key_len, z_from, res TSRMLS_CC)) {
type = res[0];
ttl = res[1];
/* open transaction on target server */
ra_index_multi(z_to, MULTI TSRMLS_CC);
switch(type) {
case REDIS_STRING:
success = ra_move_string(key, key_len, z_from, z_to, ttl TSRMLS_CC);
break;
case REDIS_SET:
success = ra_move_set(key, key_len, z_from, z_to, ttl TSRMLS_CC);
break;
case REDIS_LIST:
success = ra_move_list(key, key_len, z_from, z_to, ttl TSRMLS_CC);
break;
case REDIS_ZSET:
success = ra_move_zset(key, key_len, z_from, z_to, ttl TSRMLS_CC);
break;
case REDIS_HASH:
success = ra_move_hash(key, key_len, z_from, z_to, ttl TSRMLS_CC);
break;
default:
/* TODO: report? */
break;
}
}
if(success) {
ra_del_key(key, key_len, z_from TSRMLS_CC);
ra_index_key(key, key_len, z_to TSRMLS_CC);
}
/* close transaction */
ra_index_exec(z_to, NULL, 0 TSRMLS_CC);
}
/* callback with the current progress, with hostname and count */
static void
zval_rehash_callback(zend_fcall_info *z_cb, zend_fcall_info_cache *z_cb_cache,
const char *hostname, long count TSRMLS_DC) {
zval zv, *z_ret = &zv;
ZVAL_NULL(z_ret);
#if (PHP_MAJOR_VERSION < 7)
zval *z_host, *z_count, **z_args_pp[2];
MAKE_STD_ZVAL(z_host);
ZVAL_STRING(z_host, hostname);
z_args_pp[0] = &z_host;
MAKE_STD_ZVAL(z_count);
ZVAL_LONG(z_count, count);
z_args_pp[1] = &z_count;
z_cb->params = z_args_pp;
z_cb->retval_ptr_ptr = &z_ret;
#else
zval z_args[2];
ZVAL_STRING(&z_args[0], hostname);
ZVAL_LONG(&z_args[1], count);
z_cb->params = z_args;
z_cb->retval = z_ret;
#endif
z_cb->no_separation = 0;
z_cb->param_count = 2;
/* run cb(hostname, count) */
zend_call_function(z_cb, z_cb_cache TSRMLS_CC);
/* cleanup */
#if (PHP_MAJOR_VERSION < 7)
zval_ptr_dtor(&z_host);
zval_ptr_dtor(&z_count);
#else
zval_dtor(&z_args[0]);
#endif
zval_dtor(z_ret);
}
static void
ra_rehash_server(RedisArray *ra, zval *z_redis, const char *hostname, zend_bool b_index,
zend_fcall_info *z_cb, zend_fcall_info_cache *z_cb_cache TSRMLS_DC) {
HashTable *h_keys;
long count = 0;
zval z_fun, z_ret, z_argv, *z_ele;
/* list all keys */
if (b_index) {
ZVAL_STRING(&z_fun, "SMEMBERS");
ZVAL_STRING(&z_argv, PHPREDIS_INDEX_NAME);
} else {
ZVAL_STRING(&z_fun, "KEYS");
ZVAL_STRING(&z_argv, "*");
}
ZVAL_NULL(&z_ret);
call_user_function(&redis_ce->function_table, z_redis, &z_fun, &z_ret, 1, &z_argv);
zval_dtor(&z_argv);
zval_dtor(&z_fun);
if (Z_TYPE(z_ret) == IS_ARRAY) {
h_keys = Z_ARRVAL(z_ret);
count = zend_hash_num_elements(h_keys);
}
if (!count) {
zval_dtor(&z_ret);
return;
}
/* callback */
if(z_cb && z_cb_cache) {
zval_rehash_callback(z_cb, z_cb_cache, hostname, count TSRMLS_CC);
}
/* for each key, redistribute */
ZEND_HASH_FOREACH_VAL(h_keys, z_ele) {
int pos = 0;
/* check that we're not moving to the same node. */
zval *z_target = ra_find_node(ra, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele), &pos TSRMLS_CC);
if (z_target && strcmp(hostname, ra->hosts[pos])) { /* different host */
ra_move_key(Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele), z_redis, z_target TSRMLS_CC);
}
} ZEND_HASH_FOREACH_END();
/* cleanup */
zval_dtor(&z_ret);
}
void
ra_rehash(RedisArray *ra, zend_fcall_info *z_cb, zend_fcall_info_cache *z_cb_cache TSRMLS_DC) {
int i;
/* redistribute the data, server by server. */
if(!ra->prev)
return; /* TODO: compare the two rings for equality */
for(i = 0; i < ra->prev->count; ++i) {
ra_rehash_server(ra, &ra->prev->redis[i], ra->prev->hosts[i], ra->index, z_cb, z_cb_cache TSRMLS_CC);
}
}
redis-3.1.6/redis_array_impl.h 0000644 0001750 0000012 00000002775 13223116600 017111 0 ustar pyatsukhnenko wheel #ifndef REDIS_ARRAY_IMPL_H
#define REDIS_ARRAY_IMPL_H
#ifdef PHP_WIN32
#include
#else
#include
#endif
#include "redis_array.h"
RedisArray *ra_load_hosts(RedisArray *ra, HashTable *hosts, long retry_interval, zend_bool b_lazy_connect TSRMLS_DC);
RedisArray *ra_load_array(const char *name TSRMLS_DC);
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, double read_timeout TSRMLS_DC);
zval *ra_find_node_by_name(RedisArray *ra, const char *host, int host_len TSRMLS_DC);
zval *ra_find_node(RedisArray *ra, const char *key, int key_len, int *out_pos TSRMLS_DC);
void ra_init_function_table(RedisArray *ra);
void ra_move_key(const char *key, int key_len, zval *z_from, zval *z_to TSRMLS_DC);
void ra_index_multi(zval *z_redis, long multi_value TSRMLS_DC);
void ra_index_key(const char *key, int key_len, zval *z_redis TSRMLS_DC);
void ra_index_keys(zval *z_pairs, zval *z_redis TSRMLS_DC);
void ra_index_del(zval *z_keys, zval *z_redis TSRMLS_DC);
void ra_index_exec(zval *z_redis, zval *return_value, int keep_all TSRMLS_DC);
void ra_index_discard(zval *z_redis, zval *return_value TSRMLS_DC);
void ra_index_unwatch(zval *z_redis, zval *return_value TSRMLS_DC);
zend_bool ra_is_write_cmd(RedisArray *ra, const char *cmd, int cmd_len);
void ra_rehash(RedisArray *ra, zend_fcall_info *z_cb, zend_fcall_info_cache *z_cb_cache TSRMLS_DC);
#endif
redis-3.1.6/redis_cluster.c 0000644 0001750 0000012 00000271732 13223116600 016427 0 ustar pyatsukhnenko wheel /*
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2009 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: Michael Grunder |
| Maintainer: Nicolas Favre-Felix |
+----------------------------------------------------------------------+
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "common.h"
#include "php_redis.h"
#include "ext/standard/info.h"
#include "crc16.h"
#include "redis_cluster.h"
#include "redis_commands.h"
#include
#include "library.h"
#include
#include
zend_class_entry *redis_cluster_ce;
/* Exception handler */
zend_class_entry *redis_cluster_exception_ce;
/* Handlers for RedisCluster */
zend_object_handlers RedisCluster_handlers;
ZEND_BEGIN_ARG_INFO_EX(arginfo_ctor, 0, 0, 1)
ZEND_ARG_INFO(0, name)
ZEND_ARG_ARRAY_INFO(0, seeds, 0)
ZEND_ARG_INFO(0, timeout)
ZEND_ARG_INFO(0, read_timeout)
ZEND_ARG_INFO(0, persistent)
ZEND_END_ARG_INFO();
ZEND_BEGIN_ARG_INFO_EX(arginfo_del, 0, 0, 1)
ZEND_ARG_INFO(0, key)
#if PHP_VERSION_ID >= 50600
ZEND_ARG_VARIADIC_INFO(0, other_keys)
#else
ZEND_ARG_INFO(0, ...)
#endif
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_mget, 0, 0, 1)
ZEND_ARG_ARRAY_INFO(0, keys, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_keys, 0, 0, 1)
ZEND_ARG_INFO(0, pattern)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_cluster, 0, 0, 1)
ZEND_ARG_INFO(0, arg)
#if PHP_VERSION_ID >= 50600
ZEND_ARG_VARIADIC_INFO(0, other_args)
#else
ZEND_ARG_INFO(0, ...)
#endif
ZEND_END_ARG_INFO()
/* Argument info for HSCAN, SSCAN, HSCAN */
ZEND_BEGIN_ARG_INFO_EX(arginfo_kscan_cl, 0, 0, 2)
ZEND_ARG_INFO(0, str_key)
ZEND_ARG_INFO(1, i_iterator)
ZEND_ARG_INFO(0, str_pattern)
ZEND_ARG_INFO(0, i_count)
ZEND_END_ARG_INFO();
/* Argument infor for SCAN */
ZEND_BEGIN_ARG_INFO_EX(arginfo_scan_cl, 0, 0, 2)
ZEND_ARG_INFO(1, i_iterator)
ZEND_ARG_INFO(0, str_node)
ZEND_ARG_INFO(0, str_pattern)
ZEND_ARG_INFO(0, i_count)
ZEND_END_ARG_INFO();
/* Function table */
zend_function_entry redis_cluster_functions[] = {
PHP_ME(RedisCluster, __construct, NULL, ZEND_ACC_CTOR | ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, close, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, get, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, set, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, mget, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, mset, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, msetnx, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, del, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, setex, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, psetex, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, setnx, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, getset, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, exists, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, keys, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, type, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, lpop, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, rpop, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, lset, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, spop, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, lpush, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, rpush, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, blpop, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, brpop, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, rpushx, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, lpushx, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, linsert, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, lindex, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, lrem, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, brpoplpush, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, rpoplpush, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, llen, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, scard, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, smembers, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, sismember, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, sadd, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, saddarray, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, srem, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, sunion, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, sunionstore, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, sinter, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, sinterstore, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, sdiff, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, sdiffstore, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, srandmember, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, strlen, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, persist, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, ttl, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, pttl, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zcard, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zcount, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zremrangebyscore, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zscore, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zadd, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zincrby, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, hlen, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, hkeys, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, hvals, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, hget, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, hgetall, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, hexists, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, hincrby, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, hset, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, hsetnx, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, hmget, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, hmset, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, hdel, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, hincrbyfloat, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, hstrlen, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, dump, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zrank, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zrevrank, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, incr, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, decr, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, incrby, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, decrby, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, incrbyfloat, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, expire, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, pexpire, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, expireat, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, pexpireat, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, append, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, getbit, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, setbit, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, bitop, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, bitpos, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, bitcount, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, lget, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, getrange, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, ltrim, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, lrange, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zremrangebyrank, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, publish, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, rename, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, renamenx, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, pfcount, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, pfadd, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, pfmerge, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, setrange, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, restore, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, smove, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zrange, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zrevrange, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zrangebyscore, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zrevrangebyscore, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zrangebylex, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zrevrangebylex, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zlexcount, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zremrangebylex, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zunionstore, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zinterstore, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zrem, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, sort, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, object, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, subscribe, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, psubscribe, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, unsubscribe, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, punsubscribe, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, eval, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, evalsha, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, scan, arginfo_scan_cl, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, sscan, arginfo_kscan_cl, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, zscan, arginfo_kscan_cl, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, hscan, arginfo_kscan_cl, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, getmode, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, getlasterror, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, clearlasterror, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, getoption, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, setoption, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, _prefix, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, _serialize, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, _unserialize, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, _masters, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, _redir, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, multi, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, exec, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, discard, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, watch, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, unwatch, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, save, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, bgsave, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, flushdb, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, flushall, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, dbsize, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, bgrewriteaof, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, lastsave, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, info, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, role, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, time, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, randomkey, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, ping, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, echo, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, command, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, rawcommand, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, cluster, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, client, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, config, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, pubsub, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, script, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, slowlog, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, geoadd, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, geohash, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, geopos, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, geodist, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, georadius, NULL, ZEND_ACC_PUBLIC)
PHP_ME(RedisCluster, georadiusbymember, NULL, ZEND_ACC_PUBLIC)
PHP_FE_END
};
/* Our context seeds will be a hash table with RedisSock* pointers */
#if (PHP_MAJOR_VERSION < 7)
static void ht_free_seed(void *data)
#else
static void ht_free_seed(zval *data)
#endif
{
RedisSock *redis_sock = *(RedisSock**)data;
if(redis_sock) redis_free_socket(redis_sock);
}
/* Free redisClusterNode objects we've stored */
#if (PHP_MAJOR_VERSION < 7)
static void ht_free_node(void *data)
#else
static void ht_free_node(zval *data)
#endif
{
redisClusterNode *node = *(redisClusterNode**)data;
cluster_free_node(node);
}
/* Create redisCluster context */
#if (PHP_MAJOR_VERSION < 7)
zend_object_value
create_cluster_context(zend_class_entry *class_type TSRMLS_DC) {
redisCluster *cluster;
// Allocate our actual struct
cluster = ecalloc(1, sizeof(redisCluster));
#else
zend_object *
create_cluster_context(zend_class_entry *class_type TSRMLS_DC) {
redisCluster *cluster;
// Allocate our actual struct
cluster = ecalloc(1, sizeof(redisCluster) + sizeof(zval) * (class_type->default_properties_count - 1));
#endif
// We're not currently subscribed anywhere
cluster->subscribed_slot = -1;
// Allocate our RedisSock we'll use to store prefix/serialization flags
cluster->flags = ecalloc(1, sizeof(RedisSock));
// Allocate our hash table for seeds
ALLOC_HASHTABLE(cluster->seeds);
zend_hash_init(cluster->seeds, 0, NULL, ht_free_seed, 0);
// Allocate our hash table for connected Redis objects
ALLOC_HASHTABLE(cluster->nodes);
zend_hash_init(cluster->nodes, 0, NULL, ht_free_node, 0);
// Initialize it
zend_object_std_init(&cluster->std, class_type TSRMLS_CC);
#if (PHP_MAJOR_VERSION < 7)
zend_object_value retval;
#if PHP_VERSION_ID < 50399
zval *tmp;
zend_hash_copy(cluster->std.properties, &class_type->default_properties,
(copy_ctor_func_t)zval_add_ref, (void*)&tmp, sizeof(zval*));
#else
object_properties_init(&cluster->std, class_type);
#endif
retval.handle = zend_objects_store_put(cluster,
(zend_objects_store_dtor_t)zend_objects_destroy_object,
free_cluster_context, NULL TSRMLS_CC);
retval.handlers = zend_get_std_object_handlers();
return retval;
#else
object_properties_init(&cluster->std, class_type);
memcpy(&RedisCluster_handlers, zend_get_std_object_handlers(), sizeof(RedisCluster_handlers));
RedisCluster_handlers.offset = XtOffsetOf(redisCluster, std);
RedisCluster_handlers.free_obj = free_cluster_context;
cluster->std.handlers = &RedisCluster_handlers;
return &cluster->std;
#endif
}
/* Free redisCluster context */
void
#if (PHP_MAJOR_VERSION < 7)
free_cluster_context(void *object TSRMLS_DC) {
redisCluster *cluster = (redisCluster*)object;
#else
free_cluster_context(zend_object *object) {
redisCluster *cluster = (redisCluster*)((char*)(object) - XtOffsetOf(redisCluster, std));
#endif
// Free any allocated prefix, as well as the struct
if(cluster->flags->prefix) efree(cluster->flags->prefix);
efree(cluster->flags);
// Free seeds HashTable itself
zend_hash_destroy(cluster->seeds);
efree(cluster->seeds);
// Destroy all Redis objects and free our nodes HashTable
zend_hash_destroy(cluster->nodes);
efree(cluster->nodes);
if (cluster->err) zend_string_release(cluster->err);
zend_object_std_dtor(&cluster->std TSRMLS_CC);
#if (PHP_MAJOR_VERSION < 7)
efree(cluster);
#endif
}
/* Attempt to connect to a Redis cluster provided seeds and timeout options */
void redis_cluster_init(redisCluster *c, HashTable *ht_seeds, double timeout,
double read_timeout, int persistent TSRMLS_DC)
{
// Validate timeout
if(timeout < 0L || timeout > INT_MAX) {
zend_throw_exception(redis_cluster_exception_ce,
"Invalid timeout", 0 TSRMLS_CC);
}
// Validate our read timeout
if(read_timeout < 0L || read_timeout > INT_MAX) {
zend_throw_exception(redis_cluster_exception_ce,
"Invalid read timeout", 0 TSRMLS_CC);
}
/* Make sure there are some seeds */
if(zend_hash_num_elements(ht_seeds)==0) {
zend_throw_exception(redis_cluster_exception_ce,
"Must pass seeds", 0 TSRMLS_CC);
}
/* Set our timeout and read_timeout which we'll pass through to the
* socket type operations */
c->timeout = timeout;
c->read_timeout = read_timeout;
/* Set our option to use or not use persistent connections */
c->persistent = persistent;
/* Calculate the number of miliseconds we will wait when bouncing around,
* (e.g. a node goes down), which is not the same as a standard timeout. */
c->waitms = (long)(timeout * 1000);
// Initialize our RedisSock "seed" objects
cluster_init_seeds(c, ht_seeds);
// Create and map our key space
cluster_map_keyspace(c TSRMLS_CC);
}
/* Attempt to load a named cluster configured in php.ini */
void redis_cluster_load(redisCluster *c, char *name, int name_len TSRMLS_DC) {
zval z_seeds, z_timeout, z_read_timeout, z_persistent, *z_value;
char *iptr;
double timeout=0, read_timeout=0;
int persistent = 0;
HashTable *ht_seeds = NULL;
/* Seeds */
array_init(&z_seeds);
if ((iptr = INI_STR("redis.clusters.seeds")) != NULL) {
sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_seeds TSRMLS_CC);
}
if ((z_value = zend_hash_str_find(Z_ARRVAL(z_seeds), name, name_len)) != NULL) {
ht_seeds = Z_ARRVAL_P(z_value);
} else {
zval_dtor(&z_seeds);
zend_throw_exception(redis_cluster_exception_ce, "Couldn't find seeds for cluster", 0 TSRMLS_CC);
return;
}
/* Connection timeout */
array_init(&z_timeout);
if ((iptr = INI_STR("redis.clusters.timeout")) != NULL) {
sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_timeout TSRMLS_CC);
}
if ((z_value = zend_hash_str_find(Z_ARRVAL(z_timeout), name, name_len)) != NULL) {
if (Z_TYPE_P(z_value) == IS_STRING) {
timeout = atof(Z_STRVAL_P(z_value));
} else if (Z_TYPE_P(z_value) == IS_DOUBLE) {
timeout = Z_DVAL_P(z_value);
} else if (Z_TYPE_P(z_value) == IS_LONG) {
timeout = Z_LVAL_P(z_value);
}
}
/* Read timeout */
array_init(&z_read_timeout);
if ((iptr = INI_STR("redis.clusters.read_timeout")) != NULL) {
sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_read_timeout TSRMLS_CC);
}
if ((z_value = zend_hash_str_find(Z_ARRVAL(z_read_timeout), name, name_len)) != NULL) {
if (Z_TYPE_P(z_value) == IS_STRING) {
read_timeout = atof(Z_STRVAL_P(z_value));
} else if (Z_TYPE_P(z_value) == IS_DOUBLE) {
read_timeout = Z_DVAL_P(z_value);
} else if (Z_TYPE_P(z_value) == IS_LONG) {
read_timeout = Z_LVAL_P(z_value);
}
}
/* Persistent connections */
array_init(&z_persistent);
if ((iptr = INI_STR("redis.clusters.persistent")) != NULL) {
sapi_module.treat_data(PARSE_STRING, estrdup(iptr), &z_persistent TSRMLS_CC);
}
if ((z_value = zend_hash_str_find(Z_ARRVAL(z_persistent), name, name_len)) != NULL) {
if (Z_TYPE_P(z_value) == IS_STRING) {
persistent = atoi(Z_STRVAL_P(z_value));
} else if (Z_TYPE_P(z_value) == IS_LONG) {
persistent = Z_LVAL_P(z_value);
}
}
/* Attempt to create/connect to the cluster */
redis_cluster_init(c, ht_seeds, timeout, read_timeout, persistent TSRMLS_CC);
/* Clean up our arrays */
zval_dtor(&z_seeds);
zval_dtor(&z_timeout);
zval_dtor(&z_read_timeout);
zval_dtor(&z_persistent);
}
/*
* PHP Methods
*/
/* Create a RedisCluster Object */
PHP_METHOD(RedisCluster, __construct) {
zval *object, *z_seeds=NULL;
char *name;
strlen_t name_len;
double timeout = 0.0, read_timeout = 0.0;
zend_bool persistent = 0;
redisCluster *context = GET_CONTEXT();
// Parse arguments
if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(),
"Os!|addb", &object, redis_cluster_ce, &name,
&name_len, &z_seeds, &timeout,
&read_timeout, &persistent)==FAILURE)
{
RETURN_FALSE;
}
// Require a name
if(name_len == 0 && ZEND_NUM_ARGS() < 2) {
zend_throw_exception(redis_cluster_exception_ce,
"You must specify a name or pass seeds!",
0 TSRMLS_CC);
}
/* If we've been passed only one argument, the user is attempting to connect
* to a named cluster, stored in php.ini, otherwise we'll need manual seeds */
if (ZEND_NUM_ARGS() > 1) {
redis_cluster_init(context, Z_ARRVAL_P(z_seeds), timeout, read_timeout,
persistent TSRMLS_CC);
} else {
redis_cluster_load(context, name, name_len TSRMLS_CC);
}
}
/*
* RedisCluster method implementation
*/
/* {{{ proto bool RedisCluster::close() */
PHP_METHOD(RedisCluster, close) {
cluster_disconnect(GET_CONTEXT() TSRMLS_CC);
RETURN_TRUE;
}
/* {{{ proto string RedisCluster::get(string key) */
PHP_METHOD(RedisCluster, get) {
CLUSTER_PROCESS_KW_CMD("GET", redis_key_cmd, cluster_bulk_resp, 1);
}
/* }}} */
/* {{{ proto bool RedisCluster::set(string key, string value) */
PHP_METHOD(RedisCluster, set) {
CLUSTER_PROCESS_CMD(set, cluster_bool_resp, 0);
}
/* }}} */
/* Generic handler for MGET/MSET/MSETNX */
static int
distcmd_resp_handler(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, short slot,
clusterMultiCmd *mc, zval *z_ret, int last, cluster_cb cb)
{
clusterMultiCtx *ctx;
// Finalize multi command
cluster_multi_fini(mc);
// Spin up multi context
ctx = emalloc(sizeof(clusterMultiCtx));
ctx->z_multi = z_ret;
ctx->count = mc->argc;
ctx->last = last;
// Attempt to send the command
if(cluster_send_command(c,slot,mc->cmd.c,mc->cmd.len TSRMLS_CC)<0 ||
c->err!=NULL)
{
cluster_multi_free(mc);
zval_dtor(z_ret);
efree(ctx);
return -1;
}
if(CLUSTER_IS_ATOMIC(c)) {
// Process response now
cb(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, (void*)ctx);
} else {
CLUSTER_ENQUEUE_RESPONSE(c, slot, cb, ctx);
}
// Clear out our command but retain allocated memory
CLUSTER_MULTI_CLEAR(mc);
return 0;
}
/* Container struct for a key/value pair pulled from an array */
typedef struct clusterKeyValHT {
char kbuf[22];
char *key;
strlen_t key_len;
int key_free;
short slot;
char *val;
strlen_t val_len;
int val_free;
} clusterKeyValHT;
/* Helper to pull a key/value pair from a HashTable */
static int get_key_val_ht(redisCluster *c, HashTable *ht, HashPosition *ptr,
clusterKeyValHT *kv TSRMLS_DC)
{
zval *z_val;
zend_ulong idx;
// Grab the key, convert it to a string using provided kbuf buffer if it's
// a LONG style key
#if (PHP_MAJOR_VERSION < 7)
uint key_len;
switch(zend_hash_get_current_key_ex(ht, &(kv->key), &key_len, &idx, 0, ptr)) {
case HASH_KEY_IS_STRING:
kv->key_len = (int)(key_len-1);
#else
zend_string *zkey;
switch (zend_hash_get_current_key_ex(ht, &zkey, &idx, ptr)) {
case HASH_KEY_IS_STRING:
kv->key_len = ZSTR_LEN(zkey);
kv->key = ZSTR_VAL(zkey);
#endif
break;
case HASH_KEY_IS_LONG:
kv->key_len = snprintf(kv->kbuf,sizeof(kv->kbuf),"%ld",(long)idx);
kv->key = kv->kbuf;
break;
default:
zend_throw_exception(redis_cluster_exception_ce,
"Internal Zend HashTable error", 0 TSRMLS_CC);
return -1;
}
// Prefix our key if we need to, set the slot
kv->key_free = redis_key_prefix(c->flags, &(kv->key), &(kv->key_len));
kv->slot = cluster_hash_key(kv->key, kv->key_len);
// Now grab our value
if ((z_val = zend_hash_get_current_data_ex(ht, ptr)) == NULL) {
zend_throw_exception(redis_cluster_exception_ce,
"Internal Zend HashTable error", 0 TSRMLS_CC);
return -1;
}
// Serialize our value if required
kv->val_free = redis_serialize(c->flags,z_val,&(kv->val),&(kv->val_len)
TSRMLS_CC);
// Success
return 0;
}
/* Helper to pull, prefix, and hash a key from a HashTable value */
static int get_key_ht(redisCluster *c, HashTable *ht, HashPosition *ptr,
clusterKeyValHT *kv TSRMLS_DC)
{
zval *z_key;
if ((z_key = zend_hash_get_current_data_ex(ht, ptr)) == NULL) {
// Shouldn't happen, but check anyway
zend_throw_exception(redis_cluster_exception_ce,
"Internal Zend HashTable error", 0 TSRMLS_CC);
return -1;
}
// Always want to work with strings
convert_to_string(z_key);
kv->key = Z_STRVAL_P(z_key);
kv->key_len = Z_STRLEN_P(z_key);
kv->key_free = redis_key_prefix(c->flags, &(kv->key), &(kv->key_len));
// Hash our key
kv->slot = cluster_hash_key(kv->key, kv->key_len);
// Success
return 0;
}
/* Turn variable arguments into a HashTable for processing */
static HashTable *method_args_to_ht(zval *z_args, int argc) {
HashTable *ht_ret;
int i;
/* Allocate our hash table */
ALLOC_HASHTABLE(ht_ret);
zend_hash_init(ht_ret, argc, NULL, NULL, 0);
/* Populate our return hash table with our arguments */
for (i = 0; i < argc; i++) {
zend_hash_next_index_insert(ht_ret, &z_args[i]);
}
/* Return our hash table */
return ht_ret;
}
/* Handler for both MGET and DEL */
static int cluster_mkey_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len,
zval *z_ret, cluster_cb cb)
{
redisCluster *c = GET_CONTEXT();
clusterMultiCmd mc = {0};
clusterKeyValHT kv;
zval *z_args;
HashTable *ht_arr;
HashPosition ptr;
int i=1, argc = ZEND_NUM_ARGS(), ht_free=0;
short slot;
/* If we don't have any arguments we're invalid */
if (!argc) return -1;
/* Extract our arguments into an array */
z_args = ecalloc(argc, sizeof(zval));
if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
efree(z_args);
return -1;
}
/* Determine if we're working with a single array or variadic args */
if (argc == 1 && Z_TYPE(z_args[0]) == IS_ARRAY) {
ht_arr = Z_ARRVAL(z_args[0]);
argc = zend_hash_num_elements(ht_arr);
if (!argc) {
efree(z_args);
return -1;
}
} else {
ht_arr = method_args_to_ht(z_args, argc);
ht_free = 1;
}
/* MGET is readonly, DEL is not */
c->readonly = kw_len == 4 && CLUSTER_IS_ATOMIC(c);
// Initialize our "multi" command handler with command/len
CLUSTER_MULTI_INIT(mc, kw, kw_len);
// Process the first key outside of our loop, so we don't have to check if
// it's the first iteration every time, needlessly
zend_hash_internal_pointer_reset_ex(ht_arr, &ptr);
if(get_key_ht(c, ht_arr, &ptr, &kv TSRMLS_CC)<0) {
efree(z_args);
return -1;
}
// Process our key and add it to the command
cluster_multi_add(&mc, kv.key, kv.key_len);
// Free key if we prefixed
if(kv.key_free) efree(kv.key);
// Move to the next key
zend_hash_move_forward_ex(ht_arr, &ptr);
// Iterate over keys 2...N
slot = kv.slot;
while(zend_hash_has_more_elements_ex(ht_arr, &ptr)==SUCCESS) {
if(get_key_ht(c, ht_arr, &ptr, &kv TSRMLS_CC)<0) {
cluster_multi_free(&mc);
if (ht_free) {
zend_hash_destroy(ht_arr);
efree(ht_arr);
}
efree(z_args);
return -1;
}
// If the slots have changed, kick off the keys we've aggregated
if(slot != kv.slot) {
// Process this batch of MGET keys
if(distcmd_resp_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, slot,
&mc, z_ret, i==argc, cb)<0)
{
cluster_multi_free(&mc);
if (ht_free) {
zend_hash_destroy(ht_arr);
efree(ht_arr);
}
efree(z_args);
return -1;
}
}
// Add this key to the command
cluster_multi_add(&mc, kv.key, kv.key_len);
// Free key if we prefixed
if(kv.key_free) efree(kv.key);
// Update the last slot we encountered, and the key we're on
slot = kv.slot;
i++;
zend_hash_move_forward_ex(ht_arr, &ptr);
}
efree(z_args);
// If we've got straggler(s) process them
if(mc.argc > 0) {
if(distcmd_resp_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, slot,
&mc, z_ret, 1, cb)<0)
{
cluster_multi_free(&mc);
if (ht_free) {
zend_hash_destroy(ht_arr);
efree(ht_arr);
}
return -1;
}
}
// Free our command
cluster_multi_free(&mc);
/* Clean up our hash table if we constructed it from variadic args */
if (ht_free) {
zend_hash_destroy(ht_arr);
efree(ht_arr);
}
/* Return our object if we're in MULTI mode */
if (!CLUSTER_IS_ATOMIC(c))
RETVAL_ZVAL(getThis(), 1, 0);
// Success
return 0;
}
/* Handler for both MSET and MSETNX */
static int cluster_mset_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len,
zval *z_ret, cluster_cb cb)
{
redisCluster *c = GET_CONTEXT();
clusterKeyValHT kv;
clusterMultiCmd mc = {0};
zval *z_arr;
HashTable *ht_arr;
HashPosition ptr;
int i=1, argc;
short slot;
// Parse our arguments
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &z_arr)==FAILURE) {
return -1;
}
// No reason to send zero args
ht_arr = Z_ARRVAL_P(z_arr);
if((argc = zend_hash_num_elements(ht_arr))==0) {
return -1;
}
/* This is a write command */
c->readonly = 0;
// Set up our multi command handler
CLUSTER_MULTI_INIT(mc, kw, kw_len);
// Process the first key/value pair outside of our loop
zend_hash_internal_pointer_reset_ex(ht_arr, &ptr);
if(get_key_val_ht(c, ht_arr, &ptr, &kv TSRMLS_CC)==-1) return -1;
zend_hash_move_forward_ex(ht_arr, &ptr);
// Add this to our multi cmd, set slot, free key if we prefixed
cluster_multi_add(&mc, kv.key, kv.key_len);
cluster_multi_add(&mc, kv.val, kv.val_len);
if(kv.key_free) efree(kv.key);
if(kv.val_free) efree(kv.val);
// While we've got more keys to set
slot = kv.slot;
while(zend_hash_has_more_elements_ex(ht_arr, &ptr)==SUCCESS) {
// Pull the next key/value pair
if(get_key_val_ht(c, ht_arr, &ptr, &kv TSRMLS_CC)==-1) {
return -1;
}
// If the slots have changed, process responses
if(slot != kv.slot) {
if(distcmd_resp_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c,
slot, &mc, z_ret, i==argc, cb)<0)
{
return -1;
}
}
// Add this key and value to our command
cluster_multi_add(&mc, kv.key, kv.key_len);
cluster_multi_add(&mc, kv.val, kv.val_len);
// Free our key and value if we need to
if(kv.key_free) efree(kv.key);
if(kv.val_free) efree(kv.val);
// Update our slot, increment position
slot = kv.slot;
i++;
// Move on
zend_hash_move_forward_ex(ht_arr, &ptr);
}
// If we've got stragglers, process them too
if(mc.argc > 0) {
if(distcmd_resp_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, slot, &mc,
z_ret, 1, cb)<0)
{
return -1;
}
}
// Free our command
cluster_multi_free(&mc);
/* Return our object if we're in MULTI mode */
if (!CLUSTER_IS_ATOMIC(c))
RETVAL_ZVAL(getThis(), 1, 0);
// Success
return 0;
}
/* {{{ proto array RedisCluster::del(string key1, string key2, ... keyN) */
PHP_METHOD(RedisCluster, del) {
zval *z_ret;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_ret);
#else
z_ret = emalloc(sizeof(zval));
#endif
// Initialize a LONG value to zero for our return
ZVAL_LONG(z_ret, 0);
// Parse args, process
if(cluster_mkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DEL",
sizeof("DEL")-1, z_ret, cluster_del_resp)<0)
{
efree(z_ret);
RETURN_FALSE;
}
}
/* {{{ proto array RedisCluster::mget(array keys) */
PHP_METHOD(RedisCluster, mget) {
zval *z_ret;
// Array response
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_ret);
#else
z_ret = emalloc(sizeof(zval));
#endif
array_init(z_ret);
// Parse args, process
if(cluster_mkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "MGET",
sizeof("MGET")-1, z_ret, cluster_mbulk_mget_resp)<0)
{
zval_dtor(z_ret);
efree(z_ret);
RETURN_FALSE;
}
}
/* {{{ proto bool RedisCluster::mset(array keyvalues) */
PHP_METHOD(RedisCluster, mset) {
zval *z_ret;
// Response, defaults to TRUE
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_ret);
#else
z_ret = emalloc(sizeof(zval));
#endif
ZVAL_TRUE(z_ret);
// Parse args and process. If we get a failure, free zval and return FALSE.
if(cluster_mset_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "MSET",
sizeof("MSET")-1, z_ret, cluster_mset_resp)==-1)
{
efree(z_ret);
RETURN_FALSE;
}
}
/* {{{ proto array RedisCluster::msetnx(array keyvalues) */
PHP_METHOD(RedisCluster, msetnx) {
zval *z_ret;
// Array response
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_ret);
#else
z_ret = emalloc(sizeof(zval));
#endif
array_init(z_ret);
// Parse args and process. If we get a failure, free mem and return FALSE
if(cluster_mset_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "MSETNX",
sizeof("MSETNX")-1, z_ret, cluster_msetnx_resp)==-1)
{
zval_dtor(z_ret);
efree(z_ret);
RETURN_FALSE;
}
}
/* }}} */
/* {{{ proto bool RedisCluster::setex(string key, string value, int expiry) */
PHP_METHOD(RedisCluster, setex) {
CLUSTER_PROCESS_KW_CMD("SETEX", redis_key_long_val_cmd, cluster_bool_resp, 0);
}
/* }}} */
/* {{{ proto bool RedisCluster::psetex(string key, string value, int expiry) */
PHP_METHOD(RedisCluster, psetex) {
CLUSTER_PROCESS_KW_CMD("PSETEX", redis_key_long_val_cmd, cluster_bool_resp, 0);
}
/* }}} */
/* {{{ proto bool RedisCluster::setnx(string key, string value) */
PHP_METHOD(RedisCluster, setnx) {
CLUSTER_PROCESS_KW_CMD("SETNX", redis_kv_cmd, cluster_1_resp, 0);
}
/* }}} */
/* {{{ proto string RedisCluster::getSet(string key, string value) */
PHP_METHOD(RedisCluster, getset) {
CLUSTER_PROCESS_KW_CMD("GETSET", redis_kv_cmd, cluster_bulk_resp, 0);
}
/* }}} */
/* {{{ proto int RedisCluster::exists(string key) */
PHP_METHOD(RedisCluster, exists) {
CLUSTER_PROCESS_KW_CMD("EXISTS", redis_key_cmd, cluster_1_resp, 1);
}
/* }}} */
/* {{{ proto array Redis::keys(string pattern) */
PHP_METHOD(RedisCluster, keys) {
redisCluster *c = GET_CONTEXT();
redisClusterNode *node;
strlen_t pat_len;
char *pat, *cmd;
clusterReply *resp;
zval zv, *z_ret = &zv;
int i, cmd_len;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &pat, &pat_len)
==FAILURE)
{
RETURN_FALSE;
}
/* Prefix and then build our command */
cmd_len = redis_spprintf(c->flags, NULL TSRMLS_CC, &cmd, "KEYS", "k", pat, pat_len);
array_init(z_ret);
/* Treat as readonly */
c->readonly = CLUSTER_IS_ATOMIC(c);
/* Iterate over our known nodes */
ZEND_HASH_FOREACH_PTR(c->nodes, node) {
if (node == NULL) break;
if (cluster_send_slot(c, node->slot, cmd, cmd_len, TYPE_MULTIBULK
TSRMLS_CC)<0)
{
php_error_docref(0 TSRMLS_CC, E_ERROR, "Can't send KEYS to %s:%d",
ZSTR_VAL(node->sock->host), node->sock->port);
efree(cmd);
RETURN_FALSE;
}
/* Ensure we can get a response */
resp = cluster_read_resp(c TSRMLS_CC);
if(!resp) {
php_error_docref(0 TSRMLS_CC, E_WARNING,
"Can't read response from %s:%d", ZSTR_VAL(node->sock->host),
node->sock->port);
continue;
}
/* Iterate keys, adding to our big array */
for(i=0;ielements;i++) {
/* Skip non bulk responses, they should all be bulk */
if(resp->element[i]->type != TYPE_BULK) {
continue;
}
add_next_index_stringl(z_ret, resp->element[i]->str,
resp->element[i]->len);
}
/* Free response, don't free data */
cluster_free_reply(resp, 0);
} ZEND_HASH_FOREACH_END();
efree(cmd);
/* Return our keys */
RETURN_ZVAL(z_ret, 1, 0);
}
/* }}} */
/* {{{ proto int RedisCluster::type(string key) */
PHP_METHOD(RedisCluster, type) {
CLUSTER_PROCESS_KW_CMD("TYPE", redis_key_cmd, cluster_type_resp, 1);
}
/* }}} */
/* {{{ proto string RedisCluster::pop(string key) */
PHP_METHOD(RedisCluster, lpop) {
CLUSTER_PROCESS_KW_CMD("LPOP", redis_key_cmd, cluster_bulk_resp, 0);
}
/* }}} */
/* {{{ proto string RedisCluster::rpop(string key) */
PHP_METHOD(RedisCluster, rpop) {
CLUSTER_PROCESS_KW_CMD("RPOP", redis_key_cmd, cluster_bulk_resp, 0);
}
/* }}} */
/* {{{ proto bool RedisCluster::lset(string key, long index, string val) */
PHP_METHOD(RedisCluster, lset) {
CLUSTER_PROCESS_KW_CMD("LSET", redis_key_long_val_cmd, cluster_bool_resp, 0);
}
/* }}} */
/* {{{ proto string RedisCluster::spop(string key) */
PHP_METHOD(RedisCluster, spop) {
if (ZEND_NUM_ARGS() == 1) {
CLUSTER_PROCESS_KW_CMD("SPOP", redis_key_cmd, cluster_bulk_resp, 0);
} else if (ZEND_NUM_ARGS() == 2) {
CLUSTER_PROCESS_KW_CMD("SPOP", redis_key_long_cmd, cluster_mbulk_resp, 0);
} else {
ZEND_WRONG_PARAM_COUNT();
}
}
/* }}} */
/* {{{ proto string|array RedisCluster::srandmember(string key, [long count]) */
PHP_METHOD(RedisCluster, srandmember) {
redisCluster *c = GET_CONTEXT();
cluster_cb cb;
char *cmd; int cmd_len; short slot;
short have_count;
/* Treat as readonly */
c->readonly = CLUSTER_IS_ATOMIC(c);
if(redis_srandmember_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags,
&cmd, &cmd_len, &slot, NULL, &have_count)
==FAILURE)
{
RETURN_FALSE;
}
if(cluster_send_command(c,slot,cmd,cmd_len TSRMLS_CC)<0 || c->err!=NULL) {
efree(cmd);
RETURN_FALSE;
}
// Clean up command
efree(cmd);
cb = have_count ? cluster_mbulk_resp : cluster_bulk_resp;
if (CLUSTER_IS_ATOMIC(c)) {
cb(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
} else {
void *ctx = NULL;
CLUSTER_ENQUEUE_RESPONSE(c, slot, cb, ctx);
RETURN_ZVAL(getThis(), 1, 0);
}
}
/* {{{ proto string RedisCluster::strlen(string key) */
PHP_METHOD(RedisCluster, strlen) {
CLUSTER_PROCESS_KW_CMD("STRLEN", redis_key_cmd, cluster_long_resp, 1);
}
/* {{{ proto long RedisCluster::lpush(string key, string val1, ... valN) */
PHP_METHOD(RedisCluster, lpush) {
CLUSTER_PROCESS_KW_CMD("LPUSH", redis_key_varval_cmd, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::rpush(string key, string val1, ... valN) */
PHP_METHOD(RedisCluster, rpush) {
CLUSTER_PROCESS_KW_CMD("RPUSH", redis_key_varval_cmd, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto array RedisCluster::blpop(string key1, ... keyN, long timeout) */
PHP_METHOD(RedisCluster, blpop) {
CLUSTER_PROCESS_CMD(blpop, cluster_mbulk_resp, 0);
}
/* }}} */
/* {{{ proto array RedisCluster::brpop(string key1, ... keyN, long timeout */
PHP_METHOD(RedisCluster, brpop) {
CLUSTER_PROCESS_CMD(brpop, cluster_mbulk_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::rpushx(string key, mixed value) */
PHP_METHOD(RedisCluster, rpushx) {
CLUSTER_PROCESS_KW_CMD("RPUSHX", redis_kv_cmd, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::lpushx(string key, mixed value) */
PHP_METHOD(RedisCluster, lpushx) {
CLUSTER_PROCESS_KW_CMD("LPUSHX", redis_kv_cmd, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::linsert(string k,string pos,mix pvt,mix val) */
PHP_METHOD(RedisCluster, linsert) {
CLUSTER_PROCESS_CMD(linsert, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto string RedisCluster::lindex(string key, long index) */
PHP_METHOD(RedisCluster, lindex) {
CLUSTER_PROCESS_KW_CMD("LINDEX", redis_key_long_cmd, cluster_bulk_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::lrem(string key, long count, string val) */
PHP_METHOD(RedisCluster, lrem) {
CLUSTER_PROCESS_CMD(lrem, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto string RedisCluster::rpoplpush(string key, string key) */
PHP_METHOD(RedisCluster, rpoplpush) {
CLUSTER_PROCESS_KW_CMD("RPOPLPUSH", redis_key_key_cmd, cluster_bulk_resp, 0);
}
/* }}} */
/* {{{ proto string RedisCluster::brpoplpush(string key, string key, long tm) */
PHP_METHOD(RedisCluster, brpoplpush) {
CLUSTER_PROCESS_CMD(brpoplpush, cluster_bulk_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::llen(string key) */
PHP_METHOD(RedisCluster, llen) {
CLUSTER_PROCESS_KW_CMD("LLEN", redis_key_cmd, cluster_long_resp, 1);
}
/* }}} */
/* {{{ proto long RedisCluster::scard(string key) */
PHP_METHOD(RedisCluster, scard) {
CLUSTER_PROCESS_KW_CMD("SCARD", redis_key_cmd, cluster_long_resp, 1);
}
/* }}} */
/* {{{ proto array RedisCluster::smembers(string key) */
PHP_METHOD(RedisCluster, smembers) {
CLUSTER_PROCESS_KW_CMD("SMEMBERS", redis_key_cmd, cluster_mbulk_resp, 1);
}
/* }}} */
/* {{{ proto long RedisCluster::sismember(string key) */
PHP_METHOD(RedisCluster, sismember) {
CLUSTER_PROCESS_KW_CMD("SISMEMBER", redis_kv_cmd, cluster_1_resp, 1);
}
/* }}} */
/* {{{ proto long RedisCluster::sadd(string key, string val1 [, ...]) */
PHP_METHOD(RedisCluster, sadd) {
CLUSTER_PROCESS_KW_CMD("SADD", redis_key_varval_cmd, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::saddarray(string key, array values) */
PHP_METHOD(RedisCluster, saddarray) {
CLUSTER_PROCESS_KW_CMD("SADD", redis_key_arr_cmd, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::srem(string key, string val1 [, ...]) */
PHP_METHOD(RedisCluster, srem) {
CLUSTER_PROCESS_KW_CMD("SREM", redis_key_varval_cmd, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto array RedisCluster::sunion(string key1, ... keyN) */
PHP_METHOD(RedisCluster, sunion) {
CLUSTER_PROCESS_CMD(sunion, cluster_mbulk_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::sunionstore(string dst, string k1, ... kN) */
PHP_METHOD(RedisCluster, sunionstore) {
CLUSTER_PROCESS_CMD(sunionstore, cluster_long_resp, 0);
}
/* }}} */
/* {{{ ptoto array RedisCluster::sinter(string k1, ... kN) */
PHP_METHOD(RedisCluster, sinter) {
CLUSTER_PROCESS_CMD(sinter, cluster_mbulk_resp, 0);
}
/* }}} */
/* {{{ ptoto long RedisCluster::sinterstore(string dst, string k1, ... kN) */
PHP_METHOD(RedisCluster, sinterstore) {
CLUSTER_PROCESS_CMD(sinterstore, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto array RedisCluster::sdiff(string k1, ... kN) */
PHP_METHOD(RedisCluster, sdiff) {
CLUSTER_PROCESS_CMD(sdiff, cluster_mbulk_resp, 1);
}
/* }}} */
/* {{{ proto long RedisCluster::sdiffstore(string dst, string k1, ... kN) */
PHP_METHOD(RedisCluster, sdiffstore) {
CLUSTER_PROCESS_CMD(sdiffstore, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto bool RedisCluster::smove(sting src, string dst, string mem) */
PHP_METHOD(RedisCluster, smove) {
CLUSTER_PROCESS_CMD(smove, cluster_1_resp, 0);
}
/* }}} */
/* {{{ proto bool RedisCluster::persist(string key) */
PHP_METHOD(RedisCluster, persist) {
CLUSTER_PROCESS_KW_CMD("PERSIST", redis_key_cmd, cluster_1_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::ttl(string key) */
PHP_METHOD(RedisCluster, ttl) {
CLUSTER_PROCESS_KW_CMD("TTL", redis_key_cmd, cluster_long_resp, 1);
}
/* }}} */
/* {{{ proto long RedisCluster::pttl(string key) */
PHP_METHOD(RedisCluster, pttl) {
CLUSTER_PROCESS_KW_CMD("PTTL", redis_key_cmd, cluster_long_resp, 1);
}
/* }}} */
/* {{{ proto long RedisCluster::zcard(string key) */
PHP_METHOD(RedisCluster, zcard) {
CLUSTER_PROCESS_KW_CMD("ZCARD", redis_key_cmd, cluster_long_resp, 1);
}
/* }}} */
/* {{{ proto double RedisCluster::zscore(string key) */
PHP_METHOD(RedisCluster, zscore) {
CLUSTER_PROCESS_KW_CMD("ZSCORE", redis_kv_cmd, cluster_dbl_resp, 1);
}
/* }}} */
/* {{{ proto long RedisCluster::zadd(string key,double score,string mem, ...) */
PHP_METHOD(RedisCluster, zadd) {
CLUSTER_PROCESS_CMD(zadd, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto double RedisCluster::zincrby(string key, double by, string mem) */
PHP_METHOD(RedisCluster, zincrby) {
CLUSTER_PROCESS_CMD(zincrby, cluster_dbl_resp, 0);
}
/* }}} */
/* {{{ proto RedisCluster::zremrangebyscore(string k, string s, string e) */
PHP_METHOD(RedisCluster, zremrangebyscore) {
CLUSTER_PROCESS_KW_CMD("ZREMRANGEBYSCORE", redis_key_str_str_cmd,
cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto RedisCluster::zcount(string key, string s, string e) */
PHP_METHOD(RedisCluster, zcount) {
CLUSTER_PROCESS_KW_CMD("ZCOUNT", redis_key_str_str_cmd, cluster_long_resp, 1);
}
/* }}} */
/* {{{ proto long RedisCluster::zrank(string key, mixed member) */
PHP_METHOD(RedisCluster, zrank) {
CLUSTER_PROCESS_KW_CMD("ZRANK", redis_kv_cmd, cluster_long_resp, 1);
}
/* }}} */
/* {{{ proto long RedisCluster::zrevrank(string key, mixed member) */
PHP_METHOD(RedisCluster, zrevrank) {
CLUSTER_PROCESS_KW_CMD("ZREVRANK", redis_kv_cmd, cluster_long_resp, 1);
}
/* }}} */
/* {{{ proto long RedisCluster::hlen(string key) */
PHP_METHOD(RedisCluster, hlen) {
CLUSTER_PROCESS_KW_CMD("HLEN", redis_key_cmd, cluster_long_resp, 1);
}
/* }}} */
/* {{{ proto array RedisCluster::hkeys(string key) */
PHP_METHOD(RedisCluster, hkeys) {
CLUSTER_PROCESS_KW_CMD("HKEYS", redis_key_cmd, cluster_mbulk_raw_resp, 1);
}
/* }}} */
/* {{{ proto array RedisCluster::hvals(string key) */
PHP_METHOD(RedisCluster, hvals) {
CLUSTER_PROCESS_KW_CMD("HVALS", redis_key_cmd, cluster_mbulk_resp, 1);
}
/* }}} */
/* {{{ proto string RedisCluster::hget(string key, string mem) */
PHP_METHOD(RedisCluster, hget) {
CLUSTER_PROCESS_KW_CMD("HGET", redis_key_str_cmd, cluster_bulk_resp, 1);
}
/* }}} */
/* {{{ proto bool RedisCluster::hset(string key, string mem, string val) */
PHP_METHOD(RedisCluster, hset) {
CLUSTER_PROCESS_CMD(hset, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto bool RedisCluster::hsetnx(string key, string mem, string val) */
PHP_METHOD(RedisCluster, hsetnx) {
CLUSTER_PROCESS_CMD(hsetnx, cluster_1_resp, 0);
}
/* }}} */
/* {{{ proto array RedisCluster::hgetall(string key) */
PHP_METHOD(RedisCluster, hgetall) {
CLUSTER_PROCESS_KW_CMD("HGETALL", redis_key_cmd,
cluster_mbulk_zipstr_resp, 1);
}
/* }}} */
/* {{{ proto bool RedisCluster::hexists(string key, string member) */
PHP_METHOD(RedisCluster, hexists) {
CLUSTER_PROCESS_KW_CMD("HEXISTS", redis_key_str_cmd, cluster_1_resp, 1);
}
/* }}} */
/* {{{ proto long RedisCluster::hincr(string key, string mem, long val) */
PHP_METHOD(RedisCluster, hincrby) {
CLUSTER_PROCESS_CMD(hincrby, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto double RedisCluster::hincrbyfloat(string k, string m, double v) */
PHP_METHOD(RedisCluster, hincrbyfloat) {
CLUSTER_PROCESS_CMD(hincrbyfloat, cluster_dbl_resp, 0);
}
/* }}} */
/* {{{ proto bool RedisCluster::hmset(string key, array key_vals) */
PHP_METHOD(RedisCluster, hmset) {
CLUSTER_PROCESS_CMD(hmset, cluster_bool_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::hdel(string key, string mem1, ... memN) */
PHP_METHOD(RedisCluster, hdel) {
CLUSTER_PROCESS_CMD(hdel, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto array RedisCluster::hmget(string key, array members) */
PHP_METHOD(RedisCluster, hmget) {
CLUSTER_PROCESS_CMD(hmget, cluster_mbulk_assoc_resp, 1);
}
/* }}} */
/* {{{ proto array RedisCluster::hstrlen(string key, string field) */
PHP_METHOD(RedisCluster, hstrlen) {
CLUSTER_PROCESS_CMD(hstrlen, cluster_long_resp, 1);
}
/* }}} */
/* {{{ proto string RedisCluster::dump(string key) */
PHP_METHOD(RedisCluster, dump) {
CLUSTER_PROCESS_KW_CMD("DUMP", redis_key_cmd, cluster_bulk_raw_resp, 1);
}
/* {{{ proto long RedisCluster::incr(string key) */
PHP_METHOD(RedisCluster, incr) {
CLUSTER_PROCESS_CMD(incr, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::incrby(string key, long byval) */
PHP_METHOD(RedisCluster, incrby) {
CLUSTER_PROCESS_KW_CMD("INCRBY", redis_key_long_cmd, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::decr(string key) */
PHP_METHOD(RedisCluster, decr) {
CLUSTER_PROCESS_CMD(decr, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::decrby(string key, long byval) */
PHP_METHOD(RedisCluster, decrby) {
CLUSTER_PROCESS_KW_CMD("DECRBY", redis_key_long_cmd, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto double RedisCluster::incrbyfloat(string key, double val) */
PHP_METHOD(RedisCluster, incrbyfloat) {
CLUSTER_PROCESS_KW_CMD("INCRBYFLOAT", redis_key_dbl_cmd,
cluster_dbl_resp, 0);
}
/* }}} */
/* {{{ proto double RedisCluster::decrbyfloat(string key, double val) */
PHP_METHOD(RedisCluster, decrbyfloat) {
CLUSTER_PROCESS_KW_CMD("DECRBYFLOAT", redis_key_dbl_cmd,
cluster_dbl_resp, 0);
}
/* }}} */
/* {{{ proto bool RedisCluster::expire(string key, long sec) */
PHP_METHOD(RedisCluster, expire) {
CLUSTER_PROCESS_KW_CMD("EXPIRE", redis_key_long_cmd, cluster_1_resp, 0);
}
/* }}} */
/* {{{ proto bool RedisCluster::expireat(string key, long ts) */
PHP_METHOD(RedisCluster, expireat) {
CLUSTER_PROCESS_KW_CMD("EXPIREAT", redis_key_long_cmd, cluster_1_resp, 0);
}
/* {{{ proto bool RedisCluster::pexpire(string key, long ms) */
PHP_METHOD(RedisCluster, pexpire) {
CLUSTER_PROCESS_KW_CMD("PEXPIRE", redis_key_long_cmd, cluster_1_resp, 0);
}
/* }}} */
/* {{{ proto bool RedisCluster::pexpireat(string key, long ts) */
PHP_METHOD(RedisCluster, pexpireat) {
CLUSTER_PROCESS_KW_CMD("PEXPIREAT", redis_key_long_cmd, cluster_1_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::append(string key, string val) */
PHP_METHOD(RedisCluster, append) {
CLUSTER_PROCESS_KW_CMD("APPEND", redis_kv_cmd, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::getbit(string key, long val) */
PHP_METHOD(RedisCluster, getbit) {
CLUSTER_PROCESS_KW_CMD("GETBIT", redis_key_long_cmd, cluster_long_resp, 1);
}
/* }}} */
/* {{{ proto long RedisCluster::setbit(string key, long offset, bool onoff) */
PHP_METHOD(RedisCluster, setbit) {
CLUSTER_PROCESS_CMD(setbit, cluster_long_resp, 0);
}
/* {{{ proto long RedisCluster::bitop(string op,string key,[string key2,...]) */
PHP_METHOD(RedisCluster, bitop)
{
CLUSTER_PROCESS_CMD(bitop, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::bitcount(string key, [int start, int end]) */
PHP_METHOD(RedisCluster, bitcount) {
CLUSTER_PROCESS_CMD(bitcount, cluster_long_resp, 1);
}
/* }}} */
/* {{{ proto long RedisCluster::bitpos(string key, int bit, [int s, int end]) */
PHP_METHOD(RedisCluster, bitpos) {
CLUSTER_PROCESS_CMD(bitpos, cluster_long_resp, 1);
}
/* }}} */
/* {{{ proto string Redis::lget(string key, long index) */
PHP_METHOD(RedisCluster, lget) {
CLUSTER_PROCESS_KW_CMD("LINDEX", redis_key_long_cmd, cluster_bulk_resp, 1);
}
/* }}} */
/* {{{ proto string RedisCluster::getrange(string key, long start, long end) */
PHP_METHOD(RedisCluster, getrange) {
CLUSTER_PROCESS_KW_CMD("GETRANGE", redis_key_long_long_cmd,
cluster_bulk_resp, 1);
}
/* }}} */
/* {{{ proto string RedisCluster::ltrim(string key, long start, long end) */
PHP_METHOD(RedisCluster, ltrim) {
CLUSTER_PROCESS_KW_CMD("LTRIM", redis_key_long_long_cmd, cluster_bool_resp, 0);
}
/* }}} */
/* {{{ proto array RedisCluster::lrange(string key, long start, long end) */
PHP_METHOD(RedisCluster, lrange) {
CLUSTER_PROCESS_KW_CMD("LRANGE", redis_key_long_long_cmd,
cluster_mbulk_resp, 1);
}
/* }}} */
/* {{{ proto long RedisCluster::zremrangebyrank(string k, long s, long e) */
PHP_METHOD(RedisCluster, zremrangebyrank) {
CLUSTER_PROCESS_KW_CMD("ZREMRANGEBYRANK", redis_key_long_long_cmd,
cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::publish(string key, string msg) */
PHP_METHOD(RedisCluster, publish) {
CLUSTER_PROCESS_KW_CMD("PUBLISH", redis_key_str_cmd, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto bool RedisCluster::rename(string key1, string key2) */
PHP_METHOD(RedisCluster, rename) {
CLUSTER_PROCESS_KW_CMD("RENAME", redis_key_key_cmd, cluster_bool_resp, 0);
}
/* }}} */
/* {{{ proto bool RedisCluster::renamenx(string key1, string key2) */
PHP_METHOD(RedisCluster, renamenx) {
CLUSTER_PROCESS_KW_CMD("RENAMENX", redis_key_key_cmd, cluster_1_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::pfcount(string key) */
PHP_METHOD(RedisCluster, pfcount) {
CLUSTER_PROCESS_CMD(pfcount, cluster_long_resp, 1);
}
/* }}} */
/* {{{ proto bool RedisCluster::pfadd(string key, array vals) */
PHP_METHOD(RedisCluster, pfadd) {
CLUSTER_PROCESS_CMD(pfadd, cluster_1_resp, 0);
}
/* }}} */
/* {{{ proto bool RedisCluster::pfmerge(string key, array keys) */
PHP_METHOD(RedisCluster, pfmerge) {
CLUSTER_PROCESS_CMD(pfmerge, cluster_bool_resp, 0);
}
/* }}} */
/* {{{ proto boolean RedisCluster::restore(string key, long ttl, string val) */
PHP_METHOD(RedisCluster, restore) {
CLUSTER_PROCESS_KW_CMD("RESTORE", redis_key_long_str_cmd,
cluster_bool_resp, 0);
}
/* }}} */
/* {{{ proto long RedisCluster::setrange(string key, long offset, string val) */
PHP_METHOD(RedisCluster, setrange) {
CLUSTER_PROCESS_KW_CMD("SETRANGE", redis_key_long_str_cmd,
cluster_long_resp, 0);
}
/* }}} */
/* Generic implementation for ZRANGE, ZREVRANGE, ZRANGEBYSCORE, ZREVRANGEBYSCORE */
static void generic_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw,
zrange_cb fun)
{
redisCluster *c = GET_CONTEXT();
c->readonly = CLUSTER_IS_ATOMIC(c);
cluster_cb cb;
char *cmd; int cmd_len; short slot;
int withscores=0;
if(fun(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, kw, &cmd, &cmd_len,
&withscores, &slot, NULL)==FAILURE)
{
efree(cmd);
RETURN_FALSE;
}
if(cluster_send_command(c,slot,cmd,cmd_len TSRMLS_CC)<0 || c->err!=NULL) {
efree(cmd);
RETURN_FALSE;
}
efree(cmd);
cb = withscores ? cluster_mbulk_zipdbl_resp : cluster_mbulk_resp;
if (CLUSTER_IS_ATOMIC(c)) {
cb(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
} else {
void *ctx = NULL;
CLUSTER_ENQUEUE_RESPONSE(c, slot, cb, ctx);
RETURN_ZVAL(getThis(), 1, 0);
}
}
/* {{{ proto
* array RedisCluster::zrange(string k, long s, long e, bool score=0) */
PHP_METHOD(RedisCluster, zrange) {
generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZRANGE",
redis_zrange_cmd);
}
/* }}} */
/* {{{ proto
* array RedisCluster::zrevrange(string k,long s,long e,bool scores=0) */
PHP_METHOD(RedisCluster, zrevrange) {
generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZREVRANGE",
redis_zrange_cmd);
}
/* }}} */
/* {{{ proto array
* RedisCluster::zrangebyscore(string k, long s, long e, array opts) */
PHP_METHOD(RedisCluster, zrangebyscore) {
generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZRANGEBYSCORE",
redis_zrangebyscore_cmd);
}
/* }}} */
/* {{{ proto RedisCluster::zunionstore(string dst, array keys, [array weights,
* string agg]) */
PHP_METHOD(RedisCluster, zunionstore) {
CLUSTER_PROCESS_KW_CMD("ZUNIONSTORE", redis_zinter_cmd, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto RedisCluster::zinterstore(string dst, array keys, [array weights,
* string agg]) */
PHP_METHOD(RedisCluster, zinterstore) {
CLUSTER_PROCESS_KW_CMD("ZINTERSTORE", redis_zinter_cmd, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto RedisCluster::zrem(string key, string val1, ... valN) */
PHP_METHOD(RedisCluster, zrem) {
CLUSTER_PROCESS_KW_CMD("ZREM", redis_key_varval_cmd, cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto array
* RedisCluster::zrevrangebyscore(string k, long s, long e, array opts) */
PHP_METHOD(RedisCluster, zrevrangebyscore) {
generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZREVRANGEBYSCORE",
redis_zrangebyscore_cmd);
}
/* }}} */
/* {{{ proto array RedisCluster::zrangebylex(string key, string min, string max,
* [offset, count]) */
PHP_METHOD(RedisCluster, zrangebylex) {
CLUSTER_PROCESS_KW_CMD("ZRANGEBYLEX", redis_zrangebylex_cmd,
cluster_mbulk_resp, 1);
}
/* }}} */
/* {{{ proto array RedisCluster::zrevrangebylex(string key, string min,
* string min, [long off, long limit) */
PHP_METHOD(RedisCluster, zrevrangebylex) {
CLUSTER_PROCESS_KW_CMD("ZREVRANGEBYLEX", redis_zrangebylex_cmd,
cluster_mbulk_resp, 1);
}
/* }}} */
/* {{{ proto long RedisCluster::zlexcount(string key, string min, string max) */
PHP_METHOD(RedisCluster, zlexcount) {
CLUSTER_PROCESS_KW_CMD("ZLEXCOUNT", redis_gen_zlex_cmd, cluster_long_resp, 1);
}
/* }}} */
/* {{{ proto long RedisCluster::zremrangebylex(string key, string min, string max) */
PHP_METHOD(RedisCluster, zremrangebylex) {
CLUSTER_PROCESS_KW_CMD("ZREMRANGEBYLEX", redis_gen_zlex_cmd,
cluster_long_resp, 0);
}
/* }}} */
/* {{{ proto RedisCluster::sort(string key, array options) */
PHP_METHOD(RedisCluster, sort) {
redisCluster *c = GET_CONTEXT();
char *cmd; int cmd_len, have_store; short slot;
if(redis_sort_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, &have_store,
&cmd, &cmd_len, &slot, NULL)==FAILURE)
{
RETURN_FALSE;
}
if(cluster_send_command(c,slot,cmd,cmd_len TSRMLS_CC)<0 || c->err!=NULL) {
efree(cmd);
RETURN_FALSE;
}
efree(cmd);
// Response type differs based on presence of STORE argument
if(!have_store) {
cluster_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
} else {
cluster_long_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
}
}
/* {{{ proto RedisCluster::object(string subcmd, string key) */
PHP_METHOD(RedisCluster, object) {
redisCluster *c = GET_CONTEXT();
char *cmd; int cmd_len; short slot;
REDIS_REPLY_TYPE rtype;
if(redis_object_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, &rtype,
&cmd, &cmd_len, &slot, NULL)==FAILURE)
{
RETURN_FALSE;
}
if(cluster_send_command(c,slot,cmd,cmd_len TSRMLS_CC)<0 || c->err!=NULL) {
efree(cmd);
RETURN_FALSE;
}
efree(cmd);
// Use the correct response type
if(rtype == TYPE_INT) {
cluster_long_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
} else {
cluster_bulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
}
}
/* {{{ proto null RedisCluster::subscribe(array chans, callable cb) */
PHP_METHOD(RedisCluster, subscribe) {
CLUSTER_PROCESS_KW_CMD("SUBSCRIBE", redis_subscribe_cmd, cluster_sub_resp, 0);
}
/* }}} */
/* {{{ proto null RedisCluster::psubscribe(array pats, callable cb) */
PHP_METHOD(RedisCluster, psubscribe) {
CLUSTER_PROCESS_KW_CMD("PSUBSCRIBE", redis_subscribe_cmd, cluster_sub_resp, 0);
}
/* }}} */
static void generic_unsub_cmd(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
char *kw)
{
char *cmd;
int cmd_len;
void *ctx;
short slot;
// There is not reason to unsubscribe outside of a subscribe loop
if(c->subscribed_slot == -1) {
php_error_docref(0 TSRMLS_CC, E_WARNING,
"You can't unsubscribe outside of a subscribe loop");
RETURN_FALSE;
}
// Call directly because we're going to set the slot manually
if(redis_unsubscribe_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, kw,
&cmd, &cmd_len, &slot, &ctx)
==FAILURE)
{
RETURN_FALSE;
}
// This has to operate on our subscribe slot
if(cluster_send_slot(c, c->subscribed_slot, cmd, cmd_len, TYPE_MULTIBULK
TSRMLS_CC) ==FAILURE)
{
zend_throw_exception(redis_cluster_exception_ce,
"Failed to UNSUBSCRIBE within our subscribe loop!", 0 TSRMLS_CC);
RETURN_FALSE;
}
// Now process response from the slot we're subscribed on
cluster_unsub_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, ctx);
// Cleanup our command
efree(cmd);
}
/* {{{ proto array RedisCluster::unsubscribe(array chans) */
PHP_METHOD(RedisCluster, unsubscribe) {
generic_unsub_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, GET_CONTEXT(),
"UNSUBSCRIBE");
}
/* }}} */
/* {{{ proto array RedisCluster::punsubscribe(array pats) */
PHP_METHOD(RedisCluster, punsubscribe) {
generic_unsub_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, GET_CONTEXT(),
"PUNSUBSCRIBE");
}
/* }}} */
/* {{{ proto mixed RedisCluster::eval(string script, [array args, int numkeys) */
PHP_METHOD(RedisCluster, eval) {
CLUSTER_PROCESS_KW_CMD("EVAL", redis_eval_cmd, cluster_variant_resp, 0);
}
/* }}} */
/* {{{ proto mixed RedisCluster::evalsha(string sha, [array args, int numkeys]) */
PHP_METHOD(RedisCluster, evalsha) {
CLUSTER_PROCESS_KW_CMD("EVALSHA", redis_eval_cmd, cluster_variant_resp, 0);
}
/* }}} */
/* Commands that do not interact with Redis, but just report stuff about
* various options, etc */
/* {{{ proto string RedisCluster::getmode() */
PHP_METHOD(RedisCluster, getmode) {
redisCluster *c = GET_CONTEXT();
RETURN_LONG(c->flags->mode);
}
/* }}} */
/* {{{ proto string RedisCluster::getlasterror() */
PHP_METHOD(RedisCluster, getlasterror) {
redisCluster *c = GET_CONTEXT();
if (c->err) {
RETURN_STRINGL(ZSTR_VAL(c->err), ZSTR_LEN(c->err));
}
RETURN_NULL();
}
/* }}} */
/* {{{ proto bool RedisCluster::clearlasterror() */
PHP_METHOD(RedisCluster, clearlasterror) {
redisCluster *c = GET_CONTEXT();
if (c->err) {
zend_string_release(c->err);
c->err = NULL;
}
RETURN_TRUE;
}
/* }}} */
/* {{{ proto long RedisCluster::getOption(long option */
PHP_METHOD(RedisCluster, getoption) {
redis_getoption_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU,
GET_CONTEXT()->flags, GET_CONTEXT());
}
/* }}} */
/* {{{ proto bool RedisCluster::setOption(long option, mixed value) */
PHP_METHOD(RedisCluster, setoption) {
redis_setoption_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU,
GET_CONTEXT()->flags, GET_CONTEXT());
}
/* }}} */
/* {{{ proto string RedisCluster::_prefix(string key) */
PHP_METHOD(RedisCluster, _prefix) {
redis_prefix_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU,
GET_CONTEXT()->flags);
}
/* }}} */
/* {{{ proto string RedisCluster::_serialize(mixed val) */
PHP_METHOD(RedisCluster, _serialize) {
redis_serialize_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU,
GET_CONTEXT()->flags);
}
/* }}} */
/* {{{ proto mixed RedisCluster::_unserialize(string val) */
PHP_METHOD(RedisCluster, _unserialize) {
redis_unserialize_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU,
GET_CONTEXT()->flags, redis_cluster_exception_ce);
}
/* }}} */
/* {{{ proto array RedisCluster::_masters() */
PHP_METHOD(RedisCluster, _masters) {
redisCluster *c = GET_CONTEXT();
redisClusterNode *node;
zval zv, *z_ret = &zv;
array_init(z_ret);
ZEND_HASH_FOREACH_PTR(c->nodes, node) {
if (node == NULL) break;
zval z, *z_sub = &z;
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_sub);
#endif
array_init(z_sub);
add_next_index_stringl(z_sub, ZSTR_VAL(node->sock->host), ZSTR_LEN(node->sock->host));
add_next_index_long(z_sub, node->sock->port);
add_next_index_zval(z_ret, z_sub);
} ZEND_HASH_FOREACH_END();
RETVAL_ZVAL(z_ret, 1, 0);
}
PHP_METHOD(RedisCluster, _redir) {
redisCluster *c = GET_CONTEXT();
char buf[255];
size_t len;
len = snprintf(buf, sizeof(buf), "%s:%d", c->redir_host, c->redir_port);
if(*c->redir_host && c->redir_host_len) {
RETURN_STRINGL(buf, len);
} else {
RETURN_NULL();
}
}
/*
* Transaction handling
*/
/* {{{ proto bool RedisCluster::multi() */
PHP_METHOD(RedisCluster, multi) {
redisCluster *c = GET_CONTEXT();
if(c->flags->mode == MULTI) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"RedisCluster is already in MULTI mode, ignoring");
RETURN_FALSE;
}
/* Flag that we're in MULTI mode */
c->flags->mode = MULTI;
/* Return our object so we can chain MULTI calls */
RETVAL_ZVAL(getThis(), 1, 0);
}
/* {{{ proto bool RedisCluster::watch() */
PHP_METHOD(RedisCluster, watch) {
redisCluster *c = GET_CONTEXT();
HashTable *ht_dist;
clusterDistList *dl;
smart_string cmd = {0};
zval *z_args;
int argc = ZEND_NUM_ARGS(), i;
zend_ulong slot;
zend_string *zstr;
// Disallow in MULTI mode
if(c->flags->mode == MULTI) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"WATCH command not allowed in MULTI mode");
RETURN_FALSE;
}
// Don't need to process zero arguments
if(!argc) RETURN_FALSE;
// Create our distribution HashTable
ht_dist = cluster_dist_create();
// Allocate args, and grab them
z_args = emalloc(sizeof(zval) * argc);
if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
efree(z_args);
cluster_dist_free(ht_dist);
RETURN_FALSE;
}
// Loop through arguments, prefixing if needed
for(i=0;ilen, "WATCH", sizeof("WATCH")-1);
for (i = 0; i < dl->len; i++) {
redis_cmd_append_sstr(&cmd, dl->entry[i].key,
dl->entry[i].key_len);
}
// If we get a failure from this, we have to abort
if (cluster_send_command(c,(short)slot,cmd.c,cmd.len TSRMLS_CC)==-1) {
RETURN_FALSE;
}
// This node is watching
SLOT_SOCK(c, (short)slot)->watching = 1;
// Zero out our command buffer
cmd.len = 0;
} ZEND_HASH_FOREACH_END();
// Cleanup
cluster_dist_free(ht_dist);
efree(z_args);
efree(cmd.c);
RETURN_TRUE;
}
/* {{{ proto bool RedisCluster::unwatch() */
PHP_METHOD(RedisCluster, unwatch) {
redisCluster *c = GET_CONTEXT();
short slot;
// Send UNWATCH to nodes that need it
for(slot=0;slotmaster[slot] && SLOT_SOCK(c,slot)->watching) {
if(cluster_send_slot(c, slot, RESP_UNWATCH_CMD,
sizeof(RESP_UNWATCH_CMD)-1,
TYPE_LINE TSRMLS_CC)==-1)
{
CLUSTER_RETURN_BOOL(c, 0);
}
// No longer watching
SLOT_SOCK(c,slot)->watching = 0;
}
}
CLUSTER_RETURN_BOOL(c, 1);
}
/* {{{ proto array RedisCluster::exec() */
PHP_METHOD(RedisCluster, exec) {
redisCluster *c = GET_CONTEXT();
clusterFoldItem *fi;
// Verify we are in fact in multi mode
if(CLUSTER_IS_ATOMIC(c)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "RedisCluster is not in MULTI mode");
RETURN_FALSE;
}
// First pass, send EXEC and abort on failure
fi = c->multi_head;
while(fi) {
if(SLOT_SOCK(c, fi->slot)->mode == MULTI) {
if(cluster_send_exec(c, fi->slot TSRMLS_CC)<0) {
cluster_abort_exec(c TSRMLS_CC);
zend_throw_exception(redis_cluster_exception_ce,
"Error processing EXEC across the cluster",
0 TSRMLS_CC);
// Free our queue, reset MULTI state
CLUSTER_FREE_QUEUE(c);
CLUSTER_RESET_MULTI(c);
RETURN_FALSE;
}
SLOT_SOCK(c, fi->slot)->mode = ATOMIC;
SLOT_SOCK(c, fi->slot)->watching = 0;
}
fi = fi->next;
}
// MULTI multi-bulk response handler
cluster_multi_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
// Free our callback queue, any enqueued distributed command context items
// and reset our MULTI state.
CLUSTER_FREE_QUEUE(c);
CLUSTER_RESET_MULTI(c);
}
/* {{{ proto bool RedisCluster::discard() */
PHP_METHOD(RedisCluster, discard) {
redisCluster *c = GET_CONTEXT();
if(CLUSTER_IS_ATOMIC(c)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cluster is not in MULTI mode");
RETURN_FALSE;
}
if(cluster_abort_exec(c TSRMLS_CC)<0) {
CLUSTER_RESET_MULTI(c);
}
CLUSTER_FREE_QUEUE(c);
RETURN_TRUE;
}
/* Get a slot either by key (string) or host/port array */
static short
cluster_cmd_get_slot(redisCluster *c, zval *z_arg TSRMLS_DC)
{
strlen_t key_len;
int key_free;
zval *z_host, *z_port;
short slot;
char *key;
zend_string *zstr;
/* If it's a string, treat it as a key. Otherwise, look for a two
* element array */
if(Z_TYPE_P(z_arg)==IS_STRING || Z_TYPE_P(z_arg)==IS_LONG ||
Z_TYPE_P(z_arg)==IS_DOUBLE)
{
/* Allow for any scalar here */
zstr = zval_get_string(z_arg);
key = ZSTR_VAL(zstr);
key_len = ZSTR_LEN(zstr);
/* Hash it */
key_free = redis_key_prefix(c->flags, &key, &key_len);
slot = cluster_hash_key(key, key_len);
zend_string_release(zstr);
if(key_free) efree(key);
} else if (Z_TYPE_P(z_arg) == IS_ARRAY &&
(z_host = zend_hash_index_find(Z_ARRVAL_P(z_arg), 0)) != NULL &&
(z_port = zend_hash_index_find(Z_ARRVAL_P(z_arg), 1)) != NULL &&
Z_TYPE_P(z_host) == IS_STRING && Z_TYPE_P(z_port) == IS_LONG
) {
/* Attempt to find this specific node by host:port */
slot = cluster_find_slot(c,(const char *)Z_STRVAL_P(z_host),
(unsigned short)Z_LVAL_P(z_port));
/* Inform the caller if they've passed bad data */
if(slot < 0) {
php_error_docref(0 TSRMLS_CC, E_WARNING, "Unknown node %s:%ld",
Z_STRVAL_P(z_host), Z_LVAL_P(z_port));
}
} else {
php_error_docref(0 TSRMLS_CC, E_WARNING,
"Direted commands musty be passed a key or [host,port] array");
return -1;
}
return slot;
}
/* Generic handler for things we want directed at a given node, like SAVE,
* BGSAVE, FLUSHDB, FLUSHALL, etc */
static void
cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw,
REDIS_REPLY_TYPE reply_type, cluster_cb cb)
{
redisCluster *c = GET_CONTEXT();
char *cmd;
int cmd_len;
zval *z_arg;
short slot;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &z_arg)==FAILURE) {
RETURN_FALSE;
}
// One argument means find the node (treated like a key), and two means
// send the command to a specific host and port
slot = cluster_cmd_get_slot(c, z_arg TSRMLS_CC);
if(slot<0) {
RETURN_FALSE;
}
// Construct our command
cmd_len = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, kw, "");
// Kick off our command
if(cluster_send_slot(c, slot, cmd, cmd_len, reply_type TSRMLS_CC)<0) {
zend_throw_exception(redis_cluster_exception_ce,
"Unable to send command at a specific node", 0 TSRMLS_CC);
efree(cmd);
RETURN_FALSE;
}
// Our response callback
cb(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
// Free our command
efree(cmd);
}
/* Generic routine for handling various commands which need to be directed at
* a node, but have complex syntax. We simply parse out the arguments and send
* the command as constructed by the caller */
static void cluster_raw_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw, int kw_len)
{
redisCluster *c = GET_CONTEXT();
smart_string cmd = {0};
zval *z_args;
short slot;
int i, argc = ZEND_NUM_ARGS();
/* Commands using this pass-thru don't need to be enabled in MULTI mode */
if(!CLUSTER_IS_ATOMIC(c)) {
php_error_docref(0 TSRMLS_CC, E_WARNING,
"Command can't be issued in MULTI mode");
RETURN_FALSE;
}
/* We at least need the key or [host,port] argument */
if(argc<1) {
php_error_docref(0 TSRMLS_CC, E_WARNING,
"Command requires at least an argument to direct to a node");
RETURN_FALSE;
}
/* Allocate an array to process arguments */
z_args = emalloc(argc * sizeof(zval));
/* Grab args */
if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
efree(z_args);
RETURN_FALSE;
}
/* First argument needs to be the "where" */
if((slot = cluster_cmd_get_slot(c, &z_args[0] TSRMLS_CC))<0) {
RETURN_FALSE;
}
/* Initialize our command */
redis_cmd_init_sstr(&cmd, argc-1, kw, kw_len);
/* Iterate, appending args */
for(i=1;ireadonly = 1;
// Convert iterator to long if it isn't, update our long iterator if it's
// set and >0, and finish if it's back to zero
if(Z_TYPE_P(z_it) != IS_LONG || Z_LVAL_P(z_it)<0) {
convert_to_long(z_it);
it = 0;
} else if(Z_LVAL_P(z_it)!=0) {
it = Z_LVAL_P(z_it);
} else {
RETURN_FALSE;
}
// Apply any key prefix we have, get the slot
key_free = redis_key_prefix(c->flags, &key, &key_len);
slot = cluster_hash_key(key, key_len);
// If SCAN_RETRY is set, loop until we get a zero iterator or until
// we get non-zero elements. Otherwise we just send the command once.
do {
/* Free our return value if we're back in the loop */
if (Z_TYPE_P(return_value) == IS_ARRAY) {
zval_dtor(return_value);
ZVAL_NULL(return_value);
}
// Create command
cmd_len = redis_fmt_scan_cmd(&cmd, type, key, key_len, it, pat, pat_len,
count);
// Send it off
if(cluster_send_command(c, slot, cmd, cmd_len TSRMLS_CC)==FAILURE)
{
zend_throw_exception(redis_cluster_exception_ce,
"Couldn't send SCAN command", 0 TSRMLS_CC);
if(key_free) efree(key);
efree(cmd);
RETURN_FALSE;
}
// Read response
if(cluster_scan_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, type,
&it)==FAILURE)
{
zend_throw_exception(redis_cluster_exception_ce,
"Couldn't read SCAN response", 0 TSRMLS_CC);
if(key_free) efree(key);
efree(cmd);
RETURN_FALSE;
}
// Count the elements we got back
hash = Z_ARRVAL_P(return_value);
num_ele = zend_hash_num_elements(hash);
// Free our command
efree(cmd);
} while(c->flags->scan == REDIS_SCAN_RETRY && it != 0 && num_ele == 0);
// Free our key
if(key_free) efree(key);
// Update iterator reference
Z_LVAL_P(z_it) = it;
}
/* {{{ proto RedisCluster::scan(string master, long it [, string pat, long cnt]) */
PHP_METHOD(RedisCluster, scan) {
redisCluster *c = GET_CONTEXT();
char *cmd, *pat=NULL;
strlen_t pat_len = 0;
int cmd_len;
short slot;
zval *z_it, *z_node;
long it, num_ele;
zend_long count = 0;
/* Treat as read-only */
c->readonly = CLUSTER_IS_ATOMIC(c);
/* Can't be in MULTI mode */
if(!CLUSTER_IS_ATOMIC(c)) {
zend_throw_exception(redis_cluster_exception_ce,
"SCAN type commands can't be called in MULTI mode", 0 TSRMLS_CC);
RETURN_FALSE;
}
/* Parse arguments */
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z/z|s!l", &z_it,
&z_node, &pat, &pat_len, &count)==FAILURE)
{
RETURN_FALSE;
}
/* Convert or update iterator */
if(Z_TYPE_P(z_it) != IS_LONG || Z_LVAL_P(z_it)<0) {
convert_to_long(z_it);
it = 0;
} else if(Z_LVAL_P(z_it)!=0) {
it = Z_LVAL_P(z_it);
} else {
RETURN_FALSE;
}
/* With SCAN_RETRY on, loop until we get some keys, otherwise just return
* what Redis does, as it does */
do {
/* Free our return value if we're back in the loop */
if (Z_TYPE_P(return_value) == IS_ARRAY) {
zval_dtor(return_value);
ZVAL_NULL(return_value);
}
/* Construct our command */
cmd_len = redis_fmt_scan_cmd(&cmd, TYPE_SCAN, NULL, 0, it, pat, pat_len,
count);
if((slot = cluster_cmd_get_slot(c, z_node TSRMLS_CC))<0) {
RETURN_FALSE;
}
// Send it to the node in question
if(cluster_send_command(c, slot, cmd, cmd_len TSRMLS_CC)<0)
{
zend_throw_exception(redis_cluster_exception_ce,
"Couldn't send SCAN to node", 0 TSRMLS_CC);
efree(cmd);
RETURN_FALSE;
}
if(cluster_scan_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, TYPE_SCAN,
&it)==FAILURE || Z_TYPE_P(return_value)!=IS_ARRAY)
{
zend_throw_exception(redis_cluster_exception_ce,
"Couldn't process SCAN response from node", 0 TSRMLS_CC);
efree(cmd);
RETURN_FALSE;
}
efree(cmd);
num_ele = zend_hash_num_elements(Z_ARRVAL_P(return_value));
} while(c->flags->scan == REDIS_SCAN_RETRY && it != 0 && num_ele == 0);
Z_LVAL_P(z_it) = it;
}
/* }}} */
/* {{{ proto RedisCluster::sscan(string key, long it [string pat, long cnt]) */
PHP_METHOD(RedisCluster, sscan) {
cluster_kscan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_SSCAN);
}
/* }}} */
/* {{{ proto RedisCluster::zscan(string key, long it [string pat, long cnt]) */
PHP_METHOD(RedisCluster, zscan) {
cluster_kscan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_ZSCAN);
}
/* }}} */
/* {{{ proto RedisCluster::hscan(string key, long it [string pat, long cnt]) */
PHP_METHOD(RedisCluster, hscan) {
cluster_kscan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_HSCAN);
}
/* }}} */
/* {{{ proto RedisCluster::save(string key)
* proto RedisCluster::save(string host, long port) */
PHP_METHOD(RedisCluster, save) {
cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SAVE", TYPE_LINE,
cluster_bool_resp);
}
/* }}} */
/* {{{ proto RedisCluster::bgsave(string key)
* proto RedisCluster::bgsave(string host, long port) */
PHP_METHOD(RedisCluster, bgsave) {
cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "BGSAVE",
TYPE_LINE, cluster_bool_resp);
}
/* }}} */
/* {{{ proto RedisCluster::flushdb(string key)
* proto RedisCluster::flushdb(string host, long port) */
PHP_METHOD(RedisCluster, flushdb) {
cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "FLUSHDB",
TYPE_LINE, cluster_bool_resp);
}
/* }}} */
/* {{{ proto RedisCluster::flushall(string key)
* proto RedisCluster::flushall(string host, long port) */
PHP_METHOD(RedisCluster, flushall) {
cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "FLUSHALL",
TYPE_LINE, cluster_bool_resp);
}
/* }}} */
/* {{{ proto RedisCluster::dbsize(string key)
* proto RedisCluster::dbsize(string host, long port) */
PHP_METHOD(RedisCluster, dbsize) {
cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "DBSIZE",
TYPE_INT, cluster_long_resp);
}
/* }}} */
/* {{{ proto RedisCluster::bgrewriteaof(string key)
* proto RedisCluster::bgrewriteaof(string host, long port) */
PHP_METHOD(RedisCluster, bgrewriteaof) {
cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "BGREWRITEAOF",
TYPE_LINE, cluster_bool_resp);
}
/* }}} */
/* {{{ proto RedisCluster::lastsave(string key)
* proto RedisCluster::lastsave(array $host_port) */
PHP_METHOD(RedisCluster, lastsave) {
cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "LASTSAVE",
TYPE_INT, cluster_long_resp);
}
/* }}} */
/* {{{ proto array RedisCluster::info(string key, [string $arg])
* proto array RedisCluster::info(array host_port, [string $arg]) */
PHP_METHOD(RedisCluster, info) {
redisCluster *c = GET_CONTEXT();
REDIS_REPLY_TYPE rtype;
char *cmd, *opt=NULL;
int cmd_len;
strlen_t opt_len = 0;
void *ctx = NULL;
zval *z_arg;
short slot;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|s", &z_arg, &opt,
&opt_len)==FAILURE)
{
RETURN_FALSE;
}
/* Treat INFO as non read-only, as we probably want the master */
c->readonly = 0;
slot = cluster_cmd_get_slot(c, z_arg TSRMLS_CC);
if (slot < 0) {
RETURN_FALSE;
}
if (opt != NULL) {
cmd_len = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, "INFO", "s", opt, opt_len);
} else {
cmd_len = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, "INFO", "");
}
rtype = CLUSTER_IS_ATOMIC(c) ? TYPE_BULK : TYPE_LINE;
if (cluster_send_slot(c, slot, cmd, cmd_len, rtype TSRMLS_CC)<0) {
zend_throw_exception(redis_cluster_exception_ce,
"Unable to send INFO command to specific node", 0 TSRMLS_CC);
efree(cmd);
RETURN_FALSE;
}
if (CLUSTER_IS_ATOMIC(c)) {
cluster_info_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
} else {
CLUSTER_ENQUEUE_RESPONSE(c, slot, cluster_info_resp, ctx);
}
efree(cmd);
}
/* }}} */
/* {{{ proto array RedisCluster::client('list')
* proto bool RedisCluster::client('kill', $ipport)
* proto bool RedisCluster::client('setname', $name)
* proto string RedisCluster::client('getname')
*/
PHP_METHOD(RedisCluster, client) {
redisCluster *c = GET_CONTEXT();
char *cmd, *opt=NULL, *arg=NULL;
int cmd_len;
strlen_t opt_len, arg_len = 0;
REDIS_REPLY_TYPE rtype;
zval *z_node;
short slot;
cluster_cb cb;
/* Parse args */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zs|s", &z_node, &opt,
&opt_len, &arg, &arg_len)==FAILURE)
{
RETURN_FALSE;
}
/* Make sure we can properly resolve the slot */
slot = cluster_cmd_get_slot(c, z_node TSRMLS_CC);
if(slot<0) RETURN_FALSE;
/* Our return type and reply callback is different for all subcommands */
if (opt_len == 4 && !strncasecmp(opt, "list", 4)) {
rtype = CLUSTER_IS_ATOMIC(c) ? TYPE_BULK : TYPE_LINE;
cb = cluster_client_list_resp;
} else if ((opt_len == 4 && !strncasecmp(opt, "kill", 4)) ||
(opt_len == 7 && !strncasecmp(opt, "setname", 7)))
{
rtype = TYPE_LINE;
cb = cluster_bool_resp;
} else if (opt_len == 7 && !strncasecmp(opt, "getname", 7)) {
rtype = CLUSTER_IS_ATOMIC(c) ? TYPE_BULK : TYPE_LINE;
cb = cluster_bulk_resp;
} else {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"Invalid CLIENT subcommand (LIST, KILL, GETNAME, and SETNAME are valid");
RETURN_FALSE;
}
/* Construct the command */
if (ZEND_NUM_ARGS() == 3) {
cmd_len = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, "CLIENT", "ss",
opt, opt_len, arg, arg_len);
} else if(ZEND_NUM_ARGS() == 2) {
cmd_len = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, "CLIENT", "s",
opt, opt_len);
} else {
zend_wrong_param_count(TSRMLS_C);
RETURN_FALSE;
}
/* Attempt to write our command */
if (cluster_send_slot(c, slot, cmd, cmd_len, rtype TSRMLS_CC)<0) {
zend_throw_exception(redis_cluster_exception_ce,
"Unable to send CLIENT command to specific node", 0 TSRMLS_CC);
efree(cmd);
RETURN_FALSE;
}
/* Now enqueue or process response */
if (CLUSTER_IS_ATOMIC(c)) {
cb(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
} else {
void *ctx = NULL;
CLUSTER_ENQUEUE_RESPONSE(c, slot, cb, ctx);
}
efree(cmd);
}
/* {{{ proto mixed RedisCluster::cluster(variant) */
PHP_METHOD(RedisCluster, cluster) {
cluster_raw_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "CLUSTER",
sizeof("CLUSTER")-1);
}
/* }}} */
/* }}} */
/* {{{ proto mixed RedisCluster::config(string key, ...)
* proto mixed RedisCluster::config(array host_port, ...) */
PHP_METHOD(RedisCluster, config) {
cluster_raw_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "CONFIG",
sizeof("CONFIG")-1);
}
/* }}} */
/* {{{ proto mixed RedisCluster::pubsub(string key, ...)
* proto mixed RedisCluster::pubsub(array host_port, ...) */
PHP_METHOD(RedisCluster, pubsub) {
cluster_raw_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PUBSUB",
sizeof("PUBSUB")-1);
}
/* }}} */
/* {{{ proto mixed RedisCluster::script(string key, ...)
* proto mixed RedisCluster::script(array host_port, ...) */
PHP_METHOD(RedisCluster, script) {
cluster_raw_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SCRIPT",
sizeof("SCRIPT")-1);
}
/* }}} */
/* {{{ proto mixed RedisCluster::slowlog(string key, ...)
* proto mixed RedisCluster::slowlog(array host_port, ...) */
PHP_METHOD(RedisCluster, slowlog) {
cluster_raw_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SLOWLOG",
sizeof("SLOWLOG")-1);
}
/* }}} */
/* {{{ proto int RedisCluster::geoadd(string key, float long float lat string mem, ...) */
PHP_METHOD(RedisCluster, geoadd) {
CLUSTER_PROCESS_KW_CMD("GEOADD", redis_key_varval_cmd, cluster_long_resp, 0);
}
/* {{{ proto array RedisCluster::geohash(string key, string mem1, [string mem2...]) */
PHP_METHOD(RedisCluster, geohash) {
CLUSTER_PROCESS_KW_CMD("GEOHASH", redis_key_varval_cmd, cluster_mbulk_raw_resp, 1);
}
/* {{{ proto array RedisCluster::geopos(string key, string mem1, [string mem2...]) */
PHP_METHOD(RedisCluster, geopos) {
CLUSTER_PROCESS_KW_CMD("GEOPOS", redis_key_varval_cmd, cluster_variant_resp, 1);
}
/* {{{ proto array RedisCluster::geodist(string key, string mem1, string mem2 [string unit]) */
PHP_METHOD(RedisCluster, geodist) {
CLUSTER_PROCESS_CMD(geodist, cluster_dbl_resp, 1);
}
/* {{{ proto array RedisCluster::georadius() }}} */
PHP_METHOD(RedisCluster, georadius) {
CLUSTER_PROCESS_CMD(georadius, cluster_variant_resp, 1);
}
/* {{{ proto array RedisCluster::georadiusbymember() }}} */
PHP_METHOD(RedisCluster, georadiusbymember) {
CLUSTER_PROCESS_CMD(georadiusbymember, cluster_variant_resp, 1)
}
/* {{{ proto array RedisCluster::role(string key)
* proto array RedisCluster::role(array host_port) */
PHP_METHOD(RedisCluster, role) {
cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ROLE",
TYPE_MULTIBULK, cluster_variant_resp);
}
/* {{{ proto array RedisCluster::time(string key)
* proto array RedisCluster::time(array host_port) */
PHP_METHOD(RedisCluster, time) {
cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "TIME",
TYPE_MULTIBULK, cluster_variant_resp);
}
/* }}} */
/* {{{ proto string RedisCluster::randomkey(string key)
* proto string RedisCluster::randomkey(array host_port) */
PHP_METHOD(RedisCluster, randomkey) {
cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "RANDOMKEY",
TYPE_BULK, cluster_bulk_resp);
}
/* }}} */
/* {{{ proto bool RedisCluster::ping(string key)
* proto bool RedisCluster::ping(array host_port) */
PHP_METHOD(RedisCluster, ping) {
cluster_empty_node_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "PING",
TYPE_LINE, cluster_ping_resp);
}
/* }}} */
/* {{{ proto string RedisCluster::echo(string key, string msg)
* proto string RedisCluster::echo(array host_port, string msg) */
PHP_METHOD(RedisCluster, echo) {
redisCluster *c = GET_CONTEXT();
REDIS_REPLY_TYPE rtype;
zval *z_arg;
char *cmd, *msg;
int cmd_len;
strlen_t msg_len;
short slot;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zs", &z_arg, &msg,
&msg_len)==FAILURE)
{
RETURN_FALSE;
}
/* Treat this as a readonly command */
c->readonly = CLUSTER_IS_ATOMIC(c);
/* Grab slot either by key or host/port */
slot = cluster_cmd_get_slot(c, z_arg TSRMLS_CC);
if(slot<0) {
RETURN_FALSE;
}
/* Construct our command */
cmd_len = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, "ECHO", "s", msg, msg_len);
/* Send it off */
rtype = CLUSTER_IS_ATOMIC(c) ? TYPE_BULK : TYPE_LINE;
if(cluster_send_slot(c,slot,cmd,cmd_len,rtype TSRMLS_CC)<0) {
zend_throw_exception(redis_cluster_exception_ce,
"Unable to send commnad at the specificed node", 0 TSRMLS_CC);
efree(cmd);
RETURN_FALSE;
}
/* Process bulk response */
if (CLUSTER_IS_ATOMIC(c)) {
cluster_bulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
} else {
void *ctx = NULL;
CLUSTER_ENQUEUE_RESPONSE(c, slot, cluster_bulk_resp, ctx);
}
efree(cmd);
}
/* }}} */
/* {{{ proto mixed RedisCluster::rawcommand(string $key, string $cmd, [ $argv1 .. $argvN])
* proto mixed RedisCluster::rawcommand(array $host_port, string $cmd, [ $argv1 .. $argvN]) */
PHP_METHOD(RedisCluster, rawcommand) {
REDIS_REPLY_TYPE rtype;
int argc = ZEND_NUM_ARGS(), cmd_len;
redisCluster *c = GET_CONTEXT();
char *cmd = NULL;
zval *z_args;
short slot;
/* Sanity check on our arguments */
if (argc < 2) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"You must pass at least node information as well as at least a command.");
RETURN_FALSE;
}
z_args = emalloc(argc * sizeof(zval));
if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"Internal PHP error parsing method parameters.");
efree(z_args);
RETURN_FALSE;
} else if (redis_build_raw_cmd(&z_args[1], argc-1, &cmd, &cmd_len TSRMLS_CC) ||
(slot = cluster_cmd_get_slot(c, &z_args[0] TSRMLS_CC))<0)
{
if (cmd) efree(cmd);
efree(z_args);
RETURN_FALSE;
}
/* Free argument array */
efree(z_args);
/* Direct the command */
rtype = CLUSTER_IS_ATOMIC(c) ? TYPE_EOF : TYPE_LINE;
if (cluster_send_slot(c,slot,cmd,cmd_len,rtype TSRMLS_CC)<0) {
zend_throw_exception(redis_cluster_exception_ce,
"Unable to send command to the specified node", 0 TSRMLS_CC);
efree(cmd);
RETURN_FALSE;
}
/* Process variant response */
if (CLUSTER_IS_ATOMIC(c)) {
cluster_variant_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, NULL);
} else {
void *ctx = NULL;
CLUSTER_ENQUEUE_RESPONSE(c, slot, cluster_variant_resp, ctx);
}
efree(cmd);
}
/* }}} */
/* {{{ proto array RedisCluster::command()
* proto array RedisCluster::command('INFO', string cmd)
* proto array RedisCluster::command('GETKEYS', array cmd_args) */
PHP_METHOD(RedisCluster, command) {
CLUSTER_PROCESS_CMD(command, cluster_variant_resp, 0);
}
/* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */
redis-3.1.6/redis_cluster.h 0000644 0001750 0000012 00000023627 13223116600 016432 0 ustar pyatsukhnenko wheel #ifndef REDIS_CLUSTER_H
#define REDIS_CLUSTER_H
#include "cluster_library.h"
#include
#include
/* Redis cluster hash slots and N-1 which we'll use to find it */
#define REDIS_CLUSTER_SLOTS 16384
#define REDIS_CLUSTER_MOD (REDIS_CLUSTER_SLOTS-1)
/* Get attached object context */
#if (PHP_MAJOR_VERSION < 7)
#define GET_CONTEXT() \
((redisCluster*)zend_object_store_get_object(getThis() TSRMLS_CC))
#else
#define GET_CONTEXT() \
((redisCluster *)((char *)Z_OBJ_P(getThis()) - XtOffsetOf(redisCluster, std)))
#endif
/* Command building/processing is identical for every command */
#define CLUSTER_BUILD_CMD(name, c, cmd, cmd_len, slot) \
redis_##name##_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, &cmd, \
&cmd_len, &slot)
/* Append information required to handle MULTI commands to the tail of our MULTI
* linked list. */
#define CLUSTER_ENQUEUE_RESPONSE(c, slot, cb, ctx) \
clusterFoldItem *_item; \
_item = emalloc(sizeof(clusterFoldItem)); \
_item->callback = cb; \
_item->slot = slot; \
_item->ctx = ctx; \
_item->next = NULL; \
if(c->multi_head == NULL) { \
c->multi_head = _item; \
c->multi_curr = _item; \
} else { \
c->multi_curr->next = _item; \
c->multi_curr = _item; \
} \
/* Simple macro to free our enqueued callbacks after we EXEC */
#define CLUSTER_FREE_QUEUE(c) \
clusterFoldItem *_item = c->multi_head, *_tmp; \
while(_item) { \
_tmp = _item->next; \
efree(_item); \
_item = _tmp; \
} \
c->multi_head = c->multi_curr = NULL; \
/* Reset anything flagged as MULTI */
#define CLUSTER_RESET_MULTI(c) \
redisClusterNode *_node; \
ZEND_HASH_FOREACH_PTR(c->nodes, _node) { \
if (_node == NULL) break; \
_node->sock->watching = 0; \
_node->sock->mode = ATOMIC; \
} ZEND_HASH_FOREACH_END(); \
c->flags->watching = 0; \
c->flags->mode = ATOMIC; \
/* Simple 1-1 command -> response macro */
#define CLUSTER_PROCESS_CMD(cmdname, resp_func, readcmd) \
redisCluster *c = GET_CONTEXT(); \
c->readonly = CLUSTER_IS_ATOMIC(c) && readcmd; \
char *cmd; int cmd_len; short slot; void *ctx=NULL; \
if(redis_##cmdname##_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU,c->flags, &cmd, \
&cmd_len, &slot, &ctx)==FAILURE) { \
RETURN_FALSE; \
} \
if(cluster_send_command(c,slot,cmd,cmd_len TSRMLS_CC)<0 || c->err!=NULL) {\
efree(cmd); \
RETURN_FALSE; \
} \
efree(cmd); \
if(c->flags->mode == MULTI) { \
CLUSTER_ENQUEUE_RESPONSE(c, slot, resp_func, ctx); \
RETURN_ZVAL(getThis(), 1, 0); \
} \
resp_func(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, ctx);
/* More generic processing, where only the keyword differs */
#define CLUSTER_PROCESS_KW_CMD(kw, cmdfunc, resp_func, readcmd) \
redisCluster *c = GET_CONTEXT(); \
c->readonly = CLUSTER_IS_ATOMIC(c) && readcmd; \
char *cmd; int cmd_len; short slot; void *ctx=NULL; \
if(cmdfunc(INTERNAL_FUNCTION_PARAM_PASSTHRU, c->flags, kw, &cmd, &cmd_len,\
&slot,&ctx)==FAILURE) { \
RETURN_FALSE; \
} \
if(cluster_send_command(c,slot,cmd,cmd_len TSRMLS_CC)<0 || c->err!=NULL) { \
efree(cmd); \
RETURN_FALSE; \
} \
efree(cmd); \
if(c->flags->mode == MULTI) { \
CLUSTER_ENQUEUE_RESPONSE(c, slot, resp_func, ctx); \
RETURN_ZVAL(getThis(), 1, 0); \
} \
resp_func(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, ctx);
/* For the creation of RedisCluster specific exceptions */
PHP_REDIS_API zend_class_entry *rediscluster_get_exception_base(int root TSRMLS_DC);
#if (PHP_MAJOR_VERSION < 7)
/* Create cluster context */
zend_object_value create_cluster_context(zend_class_entry *class_type TSRMLS_DC);
/* Free cluster context struct */
void free_cluster_context(void *object TSRMLS_DC);
#else
/* Create cluster context */
zend_object *create_cluster_context(zend_class_entry *class_type TSRMLS_DC);
/* Free cluster context struct */
void free_cluster_context(zend_object *object);
#endif
/* Inittialize our class with PHP */
void init_rediscluster(TSRMLS_D);
/* RedisCluster method implementation */
PHP_METHOD(RedisCluster, __construct);
PHP_METHOD(RedisCluster, close);
PHP_METHOD(RedisCluster, get);
PHP_METHOD(RedisCluster, set);
PHP_METHOD(RedisCluster, mget);
PHP_METHOD(RedisCluster, mset);
PHP_METHOD(RedisCluster, msetnx);
PHP_METHOD(RedisCluster, mset);
PHP_METHOD(RedisCluster, del);
PHP_METHOD(RedisCluster, dump);
PHP_METHOD(RedisCluster, setex);
PHP_METHOD(RedisCluster, psetex);
PHP_METHOD(RedisCluster, setnx);
PHP_METHOD(RedisCluster, getset);
PHP_METHOD(RedisCluster, exists);
PHP_METHOD(RedisCluster, keys);
PHP_METHOD(RedisCluster, type);
PHP_METHOD(RedisCluster, persist);
PHP_METHOD(RedisCluster, lpop);
PHP_METHOD(RedisCluster, rpop);
PHP_METHOD(RedisCluster, spop);
PHP_METHOD(RedisCluster, rpush);
PHP_METHOD(RedisCluster, lpush);
PHP_METHOD(RedisCluster, blpop);
PHP_METHOD(RedisCluster, brpop);
PHP_METHOD(RedisCluster, rpushx);
PHP_METHOD(RedisCluster, lpushx);
PHP_METHOD(RedisCluster, linsert);
PHP_METHOD(RedisCluster, lindex);
PHP_METHOD(RedisCluster, lrem);
PHP_METHOD(RedisCluster, brpoplpush);
PHP_METHOD(RedisCluster, rpoplpush);
PHP_METHOD(RedisCluster, llen);
PHP_METHOD(RedisCluster, scard);
PHP_METHOD(RedisCluster, smembers);
PHP_METHOD(RedisCluster, sismember);
PHP_METHOD(RedisCluster, sadd);
PHP_METHOD(RedisCluster, saddarray);
PHP_METHOD(RedisCluster, srem);
PHP_METHOD(RedisCluster, sunion);
PHP_METHOD(RedisCluster, sunionstore);
PHP_METHOD(RedisCluster, sinter);
PHP_METHOD(RedisCluster, sinterstore);
PHP_METHOD(RedisCluster, sdiff);
PHP_METHOD(RedisCluster, sdiffstore);
PHP_METHOD(RedisCluster, strlen);
PHP_METHOD(RedisCluster, ttl);
PHP_METHOD(RedisCluster, pttl);
PHP_METHOD(RedisCluster, zcard);
PHP_METHOD(RedisCluster, zscore);
PHP_METHOD(RedisCluster, zcount);
PHP_METHOD(RedisCluster, zrem);
PHP_METHOD(RedisCluster, zremrangebyscore);
PHP_METHOD(RedisCluster, zrank);
PHP_METHOD(RedisCluster, zrevrank);
PHP_METHOD(RedisCluster, zadd);
PHP_METHOD(RedisCluster, zincrby);
PHP_METHOD(RedisCluster, hlen);
PHP_METHOD(RedisCluster, hget);
PHP_METHOD(RedisCluster, hkeys);
PHP_METHOD(RedisCluster, hvals);
PHP_METHOD(RedisCluster, hmget);
PHP_METHOD(RedisCluster, hmset);
PHP_METHOD(RedisCluster, hdel);
PHP_METHOD(RedisCluster, hgetall);
PHP_METHOD(RedisCluster, hexists);
PHP_METHOD(RedisCluster, hincrby);
PHP_METHOD(RedisCluster, hincrbyfloat);
PHP_METHOD(RedisCluster, hset);
PHP_METHOD(RedisCluster, hsetnx);
PHP_METHOD(RedisCluster, hstrlen);
PHP_METHOD(RedisCluster, incr);
PHP_METHOD(RedisCluster, decr);
PHP_METHOD(RedisCluster, incrby);
PHP_METHOD(RedisCluster, decrby);
PHP_METHOD(RedisCluster, incrbyfloat);
PHP_METHOD(RedisCluster, expire);
PHP_METHOD(RedisCluster, expireat);
PHP_METHOD(RedisCluster, pexpire);
PHP_METHOD(RedisCluster, pexpireat);
PHP_METHOD(RedisCluster, append);
PHP_METHOD(RedisCluster, getbit);
PHP_METHOD(RedisCluster, setbit);
PHP_METHOD(RedisCluster, bitop);
PHP_METHOD(RedisCluster, bitpos);
PHP_METHOD(RedisCluster, bitcount);
PHP_METHOD(RedisCluster, lget);
PHP_METHOD(RedisCluster, getrange);
PHP_METHOD(RedisCluster, ltrim);
PHP_METHOD(RedisCluster, lrange);
PHP_METHOD(RedisCluster, zremrangebyrank);
PHP_METHOD(RedisCluster, publish);
PHP_METHOD(RedisCluster, lset);
PHP_METHOD(RedisCluster, rename);
PHP_METHOD(RedisCluster, renamenx);
PHP_METHOD(RedisCluster, pfcount);
PHP_METHOD(RedisCluster, pfadd);
PHP_METHOD(RedisCluster, pfmerge);
PHP_METHOD(RedisCluster, restore);
PHP_METHOD(RedisCluster, setrange);
PHP_METHOD(RedisCluster, smove);
PHP_METHOD(RedisCluster, srandmember);
PHP_METHOD(RedisCluster, zrange);
PHP_METHOD(RedisCluster, zrevrange);
PHP_METHOD(RedisCluster, zrangebyscore);
PHP_METHOD(RedisCluster, zrevrangebyscore);
PHP_METHOD(RedisCluster, zrangebylex);
PHP_METHOD(RedisCluster, zrevrangebylex);
PHP_METHOD(RedisCluster, zlexcount);
PHP_METHOD(RedisCluster, zremrangebylex);
PHP_METHOD(RedisCluster, zunionstore);
PHP_METHOD(RedisCluster, zinterstore);
PHP_METHOD(RedisCluster, sort);
PHP_METHOD(RedisCluster, object);
PHP_METHOD(RedisCluster, subscribe);
PHP_METHOD(RedisCluster, psubscribe);
PHP_METHOD(RedisCluster, unsubscribe);
PHP_METHOD(RedisCluster, punsubscribe);
PHP_METHOD(RedisCluster, eval);
PHP_METHOD(RedisCluster, evalsha);
PHP_METHOD(RedisCluster, info);
PHP_METHOD(RedisCluster, cluster);
PHP_METHOD(RedisCluster, client);
PHP_METHOD(RedisCluster, config);
PHP_METHOD(RedisCluster, pubsub);
PHP_METHOD(RedisCluster, script);
PHP_METHOD(RedisCluster, slowlog);
PHP_METHOD(RedisCluster, command);
PHP_METHOD(RedisCluster, geoadd);
PHP_METHOD(RedisCluster, geohash);
PHP_METHOD(RedisCluster, geopos);
PHP_METHOD(RedisCluster, geodist);
PHP_METHOD(RedisCluster, georadius);
PHP_METHOD(RedisCluster, georadiusbymember);
/* SCAN and friends */
PHP_METHOD(RedisCluster, scan);
PHP_METHOD(RedisCluster, zscan);
PHP_METHOD(RedisCluster, hscan);
PHP_METHOD(RedisCluster, sscan);
/* Transactions */
PHP_METHOD(RedisCluster, multi);
PHP_METHOD(RedisCluster, exec);
PHP_METHOD(RedisCluster, discard);
PHP_METHOD(RedisCluster, watch);
PHP_METHOD(RedisCluster, unwatch);
/* Commands we direct to a node */
PHP_METHOD(RedisCluster, save);
PHP_METHOD(RedisCluster, bgsave);
PHP_METHOD(RedisCluster, flushdb);
PHP_METHOD(RedisCluster, flushall);
PHP_METHOD(RedisCluster, dbsize);
PHP_METHOD(RedisCluster, bgrewriteaof);
PHP_METHOD(RedisCluster, lastsave);
PHP_METHOD(RedisCluster, role);
PHP_METHOD(RedisCluster, time);
PHP_METHOD(RedisCluster, randomkey);
PHP_METHOD(RedisCluster, ping);
PHP_METHOD(RedisCluster, echo);
PHP_METHOD(RedisCluster, rawcommand);
/* Introspection */
PHP_METHOD(RedisCluster, getmode);
PHP_METHOD(RedisCluster, getlasterror);
PHP_METHOD(RedisCluster, clearlasterror);
PHP_METHOD(RedisCluster, getoption);
PHP_METHOD(RedisCluster, setoption);
PHP_METHOD(RedisCluster, _prefix);
PHP_METHOD(RedisCluster, _serialize);
PHP_METHOD(RedisCluster, _unserialize);
PHP_METHOD(RedisCluster, _masters);
PHP_METHOD(RedisCluster, _redir);
#endif
redis-3.1.6/cluster_library.c 0000644 0001750 0000012 00000223147 13223116600 016762 0 ustar pyatsukhnenko wheel #include "php_redis.h"
#include "common.h"
#include "library.h"
#include "redis_commands.h"
#include "cluster_library.h"
#include "crc16.h"
#include
extern zend_class_entry *redis_cluster_exception_ce;
/* Debugging methods/
static void cluster_dump_nodes(redisCluster *c) {
redisClusterNode *p;
ZEND_HASH_FOREACH_PTR(c->nodes, p) {
if (p == NULL) {
continue;
}
const char *slave = (p->slave) ? "slave" : "master";
php_printf("%d %s %d %d", p->sock->port, slave,p->sock->prefix_len,
p->slot);
php_printf("\n");
} ZEND_HASH_FOREACH_END();
}
static void cluster_log(char *fmt, ...)
{
va_list args;
char buffer[1024];
va_start(args, fmt);
vsnprintf(buffer,sizeof(buffer),fmt,args);
va_end(args);
fprintf(stderr, "%s\n", buffer);
}
// Debug function to dump a clusterReply structure recursively
static void dump_reply(clusterReply *reply, int indent) {
smart_string buf = {0};
int i;
switch(reply->type) {
case TYPE_ERR:
smart_string_appendl(&buf, "(error) ", sizeof("(error) ")-1);
smart_string_appendl(&buf, reply->str, reply->len);
break;
case TYPE_LINE:
smart_string_appendl(&buf, reply->str, reply->len);
break;
case TYPE_INT:
smart_string_appendl(&buf, "(integer) ", sizeof("(integer) ")-1);
smart_string_append_long(&buf, reply->integer);
break;
case TYPE_BULK:
smart_string_appendl(&buf,"\"", 1);
smart_string_appendl(&buf, reply->str, reply->len);
smart_string_appendl(&buf, "\"", 1);
break;
case TYPE_MULTIBULK:
if(reply->elements == (size_t)-1) {
smart_string_appendl(&buf, "(nil)", sizeof("(nil)")-1);
} else {
for(i=0;ielements;i++) {
dump_reply(reply->element[i], indent+2);
}
}
break;
default:
break;
}
if(buf.len > 0) {
for(i=0;itype) {
case TYPE_ERR:
case TYPE_LINE:
case TYPE_BULK:
if(free_data && reply->str)
efree(reply->str);
break;
case TYPE_MULTIBULK:
for(i=0;ielements && reply->element[i]; i++) {
cluster_free_reply(reply->element[i], free_data);
}
efree(reply->element);
break;
default:
break;
}
efree(reply);
}
static void
cluster_multibulk_resp_recursive(RedisSock *sock, size_t elements,
clusterReply **element, int *err TSRMLS_DC)
{
int i;
size_t sz;
clusterReply *r;
long len;
char buf[1024];
for (i = 0; i < elements; i++) {
r = element[i] = ecalloc(1, sizeof(clusterReply));
// Bomb out, flag error condition on a communication failure
if(redis_read_reply_type(sock, &r->type, &len TSRMLS_CC)<0) {
*err = 1;
return;
}
/* Set our reply len */
r->len = len;
switch(r->type) {
case TYPE_ERR:
case TYPE_LINE:
if(redis_sock_gets(sock,buf,sizeof(buf),&sz TSRMLS_CC)<0) {
*err = 1;
return;
}
r->len = (long long)sz;
break;
case TYPE_INT:
r->integer = len;
break;
case TYPE_BULK:
if (r->len > 0) {
r->str = redis_sock_read_bulk_reply(sock,r->len TSRMLS_CC);
if(!r->str) {
*err = 1;
return;
}
}
break;
case TYPE_MULTIBULK:
r->element = ecalloc(r->len,sizeof(clusterReply*));
r->elements = r->len;
cluster_multibulk_resp_recursive(sock, r->elements, r->element,
err TSRMLS_CC);
if(*err) return;
break;
default:
*err = 1;
return;
}
}
}
/* Return the socket for a slot and slave index */
static RedisSock *cluster_slot_sock(redisCluster *c, unsigned short slot,
ulong slaveidx)
{
redisClusterNode *node;
/* Return the master if we're not looking for a slave */
if (slaveidx == 0) {
return SLOT_SOCK(c, slot);
}
/* Abort if we can't find this slave */
if (!SLOT_SLAVES(c, slot) ||
(node = zend_hash_index_find_ptr(SLOT_SLAVES(c,slot), slaveidx)) == NULL
) {
return NULL;
}
/* Success, return the slave */
return node->sock;
}
/* Read the response from a cluster */
clusterReply *cluster_read_resp(redisCluster *c TSRMLS_DC) {
return cluster_read_sock_resp(c->cmd_sock,c->reply_type,c->reply_len TSRMLS_CC);
}
/* Read any sort of response from the socket, having already issued the
* command and consumed the reply type and meta info (length) */
clusterReply*
cluster_read_sock_resp(RedisSock *redis_sock, REDIS_REPLY_TYPE type,
size_t len TSRMLS_DC)
{
clusterReply *r;
r = ecalloc(1, sizeof(clusterReply));
r->type = type;
// Error flag in case we go recursive
int err = 0;
switch(r->type) {
case TYPE_INT:
r->integer = len;
break;
case TYPE_LINE:
case TYPE_ERR:
return r;
case TYPE_BULK:
r->len = len;
r->str = redis_sock_read_bulk_reply(redis_sock, len TSRMLS_CC);
if(r->len != -1 && !r->str) {
cluster_free_reply(r, 1);
return NULL;
}
break;
case TYPE_MULTIBULK:
r->elements = len;
if(len != (size_t)-1) {
r->element = ecalloc(len, sizeof(clusterReply*)*len);
cluster_multibulk_resp_recursive(redis_sock, len, r->element,
&err TSRMLS_CC);
}
break;
default:
cluster_free_reply(r,1);
return NULL;
}
// Free/return null on communication error
if(err) {
cluster_free_reply(r,1);
return NULL;
}
// Success, return the reply
return r;
}
/*
* Helpers to send various 'control type commands to a specific node, e.g.
* MULTI, ASKING, READONLY, READWRITE, etc
*/
/* Send a command to the specific socket and validate reply type */
static int cluster_send_direct(RedisSock *redis_sock, char *cmd, int cmd_len,
REDIS_REPLY_TYPE type TSRMLS_DC)
{
char buf[1024];
/* Connect to the socket if we aren't yet */
CLUSTER_LAZY_CONNECT(redis_sock);
/* Send our command, validate the reply type, and consume the first line */
if (!CLUSTER_SEND_PAYLOAD(redis_sock,cmd,cmd_len) ||
!CLUSTER_VALIDATE_REPLY_TYPE(redis_sock, type) ||
!php_stream_gets(redis_sock->stream, buf, sizeof(buf))) return -1;
/* Success! */
return 0;
}
static int cluster_send_asking(RedisSock *redis_sock TSRMLS_DC) {
return cluster_send_direct(redis_sock, RESP_ASKING_CMD,
sizeof(RESP_ASKING_CMD)-1, TYPE_LINE TSRMLS_CC);
}
/* Send READONLY to a specific RedisSock unless it's already flagged as being
* in READONLY mode. If we can send the command, we flag the socket as being
* in that mode. */
static int cluster_send_readonly(RedisSock *redis_sock TSRMLS_DC) {
int ret;
/* We don't have to do anything if we're already in readonly mode */
if (redis_sock->readonly) return 0;
/* Return success if we can send it */
ret = cluster_send_direct(redis_sock, RESP_READONLY_CMD,
sizeof(RESP_READONLY_CMD)-1, TYPE_LINE TSRMLS_CC);
/* Flag this socket as READONLY if our command worked */
redis_sock->readonly = !ret;
/* Return the result of our send */
return ret;
}
/* Send MULTI to a specific ReidsSock */
static int cluster_send_multi(redisCluster *c, short slot TSRMLS_DC) {
if (cluster_send_direct(SLOT_SOCK(c,slot), RESP_MULTI_CMD,
sizeof(RESP_MULTI_CMD)-1, TYPE_LINE TSRMLS_CC)==0)
{
c->cmd_sock->mode = MULTI;
return 0;
}
return -1;
}
/* Send EXEC to a given slot. We can use the normal command processing mechanism
* here because we know we'll only have sent MULTI to the master nodes. We can't
* failover inside a transaction, as we don't know if the transaction will only
* be readonly commands, or contain write commands as well */
PHP_REDIS_API int cluster_send_exec(redisCluster *c, short slot TSRMLS_DC) {
int retval;
/* Send exec */
retval = cluster_send_slot(c, slot, RESP_EXEC_CMD, sizeof(RESP_EXEC_CMD)-1,
TYPE_MULTIBULK TSRMLS_CC);
/* We'll either get a length corresponding to the number of commands sent to
* this node, or -1 in the case of EXECABORT or WATCH failure. */
c->multi_len[slot] = c->reply_len > 0 ? 1 : -1;
/* Return our retval */
return retval;
}
PHP_REDIS_API int cluster_send_discard(redisCluster *c, short slot TSRMLS_DC) {
if (cluster_send_direct(SLOT_SOCK(c,slot), RESP_DISCARD_CMD,
sizeof(RESP_DISCARD_CMD)-1, TYPE_LINE TSRMLS_CC))
{
return 0;
}
return -1;
}
/*
* Cluster key distribution helpers. For a small handlful of commands, we want
* to distribute them across 1-N nodes. These methods provide simple containers
* for the purposes of splitting keys/values in this way
* */
/* Free cluster distribution list inside a HashTable */
#if (PHP_MAJOR_VERSION < 7)
static void cluster_dist_free_ht(void *p)
#else
static void cluster_dist_free_ht(zval *p)
#endif
{
clusterDistList *dl = *(clusterDistList**)p;
int i;
for(i=0; i < dl->len; i++) {
if(dl->entry[i].key_free)
efree(dl->entry[i].key);
if(dl->entry[i].val_free)
efree(dl->entry[i].val);
}
efree(dl->entry);
efree(dl);
}
/* Spin up a HashTable that will contain distribution lists */
HashTable *cluster_dist_create() {
HashTable *ret;
ALLOC_HASHTABLE(ret);
zend_hash_init(ret, 0, NULL, cluster_dist_free_ht, 0);
return ret;
}
/* Free distribution list */
void cluster_dist_free(HashTable *ht) {
zend_hash_destroy(ht);
efree(ht);
}
/* Create a clusterDistList object */
static clusterDistList *cluster_dl_create() {
clusterDistList *dl;
dl = emalloc(sizeof(clusterDistList));
dl->entry = emalloc(CLUSTER_KEYDIST_ALLOC * sizeof(clusterKeyVal));
dl->size = CLUSTER_KEYDIST_ALLOC;
dl->len = 0;
return dl;
}
/* Add a key to a dist list, returning the keval entry */
static clusterKeyVal *cluster_dl_add_key(clusterDistList *dl, char *key,
int key_len, int key_free)
{
// Reallocate if required
if(dl->len==dl->size) {
dl->entry = erealloc(dl->entry, sizeof(clusterKeyVal) * dl->size * 2);
dl->size *= 2;
}
// Set key info
dl->entry[dl->len].key = key;
dl->entry[dl->len].key_len = key_len;
dl->entry[dl->len].key_free = key_free;
// NULL out any values
dl->entry[dl->len].val = NULL;
dl->entry[dl->len].val_len = 0;
dl->entry[dl->len].val_free = 0;
return &(dl->entry[dl->len++]);
}
/* Add a key, returning a pointer to the entry where passed for easy adding
* of values to match this key */
int cluster_dist_add_key(redisCluster *c, HashTable *ht, char *key,
strlen_t key_len, clusterKeyVal **kv)
{
int key_free;
short slot;
clusterDistList *dl;
clusterKeyVal *retptr;
// Prefix our key and hash it
key_free = redis_key_prefix(c->flags, &key, &key_len);
slot = cluster_hash_key(key, key_len);
// We can't do this if we don't fully understand the keyspace
if(c->master[slot] == NULL) {
if(key_free) efree(key);
return FAILURE;
}
// Look for this slot
if ((dl = zend_hash_index_find_ptr(ht, (zend_ulong)slot)) == NULL) {
dl = cluster_dl_create();
zend_hash_index_update_ptr(ht, (zend_ulong)slot, dl);
}
// Now actually add this key
retptr = cluster_dl_add_key(dl, key, key_len, key_free);
// Push our return pointer if requested
if(kv) *kv = retptr;
return SUCCESS;
}
/* Provided a clusterKeyVal, add a value */
void cluster_dist_add_val(redisCluster *c, clusterKeyVal *kv, zval *z_val
TSRMLS_DC)
{
char *val;
strlen_t val_len;
int val_free;
// Serialize our value
val_free = redis_serialize(c->flags, z_val, &val, &val_len TSRMLS_CC);
// Attach it to the provied keyval entry
kv->val = val;
kv->val_len = val_len;
kv->val_free = val_free;
}
/* Free allocated memory for a clusterMultiCmd */
void cluster_multi_free(clusterMultiCmd *mc) {
efree(mc->cmd.c);
efree(mc->args.c);
}
/* Add an argument to a clusterMultiCmd */
void cluster_multi_add(clusterMultiCmd *mc, char *data, int data_len) {
mc->argc++;
redis_cmd_append_sstr(&(mc->args), data, data_len);
}
/* Finalize a clusterMutliCmd by constructing the whole thing */
void cluster_multi_fini(clusterMultiCmd *mc) {
mc->cmd.len = 0;
redis_cmd_init_sstr(&(mc->cmd), mc->argc, mc->kw, mc->kw_len);
smart_string_appendl(&(mc->cmd), mc->args.c, mc->args.len);
}
/* Set our last error string encountered */
static void
cluster_set_err(redisCluster *c, char *err, int err_len)
{
// Free our last error
if (c->err != NULL) {
zend_string_release(c->err);
c->err = NULL;
}
if (err != NULL && err_len > 0) {
c->err = zend_string_init(err, err_len, 0);
if (err_len >= sizeof("CLUSTERDOWN") - 1 &&
!memcmp(err, "CLUSTERDOWN", sizeof("CLUSTERDOWN") - 1)
) {
c->clusterdown = 1;
}
}
}
/* Destructor for slaves */
#if (PHP_MAJOR_VERSION < 7)
static void ht_free_slave(void *data)
#else
static void ht_free_slave(zval *data)
#endif
{
if(*(redisClusterNode**)data) {
cluster_free_node(*(redisClusterNode**)data);
}
}
/* Get the hash slot for a given key */
unsigned short cluster_hash_key(const char *key, int len) {
int s, e;
// Find first occurrence of {, if any
for(s=0;s 1) {
r = ((int)((double)n-- * (rand() / (RAND_MAX+1.0))));
temp = array[n];
array[n] = array[r];
array[r] = temp;
};
}
/* Execute a CLUSTER SLOTS command against the seed socket, and return the
* reply or NULL on failure. */
clusterReply* cluster_get_slots(RedisSock *redis_sock TSRMLS_DC)
{
clusterReply *r;
REDIS_REPLY_TYPE type;
long len;
// Send the command to the socket and consume reply type
if(redis_sock_write(redis_sock, RESP_CLUSTER_SLOTS_CMD,
sizeof(RESP_CLUSTER_SLOTS_CMD)-1 TSRMLS_CC)<0 ||
redis_read_reply_type(redis_sock, &type, &len TSRMLS_CC)<0)
{
return NULL;
}
// Consume the rest of our response
if((r = cluster_read_sock_resp(redis_sock, type, len TSRMLS_CC))==NULL ||
r->type != TYPE_MULTIBULK || r->elements < 1)
{
if(r) cluster_free_reply(r, 1);
return NULL;
}
// Return our reply
return r;
}
/* Create a cluster node */
static redisClusterNode*
cluster_node_create(redisCluster *c, char *host, size_t host_len,
unsigned short port, unsigned short slot, short slave)
{
redisClusterNode *node = emalloc(sizeof(redisClusterNode));
// It lives in at least this slot, flag slave status
node->slot = slot;
node->slave = slave;
node->slaves = NULL;
// Attach socket
node->sock = redis_sock_create(host, host_len, port, c->timeout,
c->read_timeout, c->persistent, NULL, 0, 1);
return node;
}
/* Attach a slave to a master */
PHP_REDIS_API int
cluster_node_add_slave(redisClusterNode *master, redisClusterNode *slave)
{
ulong index;
// Allocate our slaves hash table if we haven't yet
if(!master->slaves) {
ALLOC_HASHTABLE(master->slaves);
zend_hash_init(master->slaves, 0, NULL, ht_free_slave, 0);
index = 1;
} else {
index = master->slaves->nNextFreeElement;
}
return zend_hash_index_update_ptr(master->slaves, index, slave) != NULL;
}
/* Sanity check/validation for CLUSTER SLOTS command */
#define VALIDATE_SLOTS_OUTER(r) \
(r->elements>=3 && r2->element[0]->type == TYPE_INT && \
r->element[1]->type==TYPE_INT)
#define VALIDATE_SLOTS_INNER(r) \
(r->type == TYPE_MULTIBULK && r->elements>=2 && \
r->element[0]->type == TYPE_BULK && r->element[1]->type==TYPE_INT)
/* Use the output of CLUSTER SLOTS to map our nodes */
static int cluster_map_slots(redisCluster *c, clusterReply *r) {
int i,j, hlen, klen;
short low, high;
clusterReply *r2, *r3;
redisClusterNode *pnode, *master, *slave;
unsigned short port;
char *host, key[1024];
for(i=0;ielements;i++) {
// Inner response
r2 = r->element[i];
// Validate outer and master slot
if(!VALIDATE_SLOTS_OUTER(r2) || !VALIDATE_SLOTS_INNER(r2->element[2])) {
return -1;
}
// Master
r3 = r2->element[2];
// Grab our slot range, as well as master host/port
low = (unsigned short)r2->element[0]->integer;
high = (unsigned short)r2->element[1]->integer;
host = r3->element[0]->str;
hlen = r3->element[0]->len;
port = (unsigned short)r3->element[1]->integer;
// If the node is new, create and add to nodes. Otherwise use it.
klen = snprintf(key,sizeof(key),"%s:%ld",host,port);
if ((pnode = zend_hash_str_find_ptr(c->nodes, key, klen)) == NULL) {
master = cluster_node_create(c, host, hlen, port, low, 0);
zend_hash_str_update_ptr(c->nodes, key, klen, master);
} else {
master = pnode;
}
// Attach slaves
for(j=3;jelements;j++) {
r3 = r2->element[j];
if(!VALIDATE_SLOTS_INNER(r3)) {
return -1;
}
// Skip slaves where the host is ""
if(r3->element[0]->len == 0) continue;
// Attach this node to our slave
slave = cluster_node_create(c, r3->element[0]->str,
(int)r3->element[0]->len,
(unsigned short)r3->element[1]->integer, low, 1);
cluster_node_add_slave(master, slave);
}
// Attach this node to each slot in the range
for(j=low;j<=high;j++) {
c->master[j]=master;
}
}
// Success
return 0;
}
/* Free a redisClusterNode structure */
PHP_REDIS_API void cluster_free_node(redisClusterNode *node) {
if(node->slaves) {
zend_hash_destroy(node->slaves);
efree(node->slaves);
}
redis_free_socket(node->sock);
efree(node);
}
/* Get or create a redisClusterNode that corresponds to the asking redirection */
static redisClusterNode *cluster_get_asking_node(redisCluster *c TSRMLS_DC) {
redisClusterNode *pNode;
char key[1024];
int key_len;
/* Hashed by host:port */
key_len = snprintf(key, sizeof(key), "%s:%u", c->redir_host, c->redir_port);
/* See if we've already attached to it */
if ((pNode = zend_hash_str_find_ptr(c->nodes, key, key_len)) != NULL) {
return pNode;
}
/* This host:port is unknown to us, so add it */
pNode = cluster_node_create(c, c->redir_host, c->redir_host_len,
c->redir_port, c->redir_slot, 0);
/* Return the node */
return pNode;
}
/* Get or create a node at the host:port we were asked to check, and return the
* redis_sock for it. */
static RedisSock *cluster_get_asking_sock(redisCluster *c TSRMLS_DC) {
return cluster_get_asking_node(c TSRMLS_CC)->sock;
}
/* Our context seeds will be a hash table with RedisSock* pointers */
#if (PHP_MAJOR_VERSION < 7)
static void ht_free_seed(void *data)
#else
static void ht_free_seed(zval *data)
#endif
{
RedisSock *redis_sock = *(RedisSock**)data;
if(redis_sock) redis_free_socket(redis_sock);
}
/* Free redisClusterNode objects we've stored */
#if (PHP_MAJOR_VERSION < 7)
static void ht_free_node(void *data)
#else
static void ht_free_node(zval *data)
#endif
{
redisClusterNode *node = *(redisClusterNode**)data;
cluster_free_node(node);
}
/* Construct a redisCluster object */
PHP_REDIS_API redisCluster *cluster_create(double timeout, double read_timeout,
int failover, int persistent)
{
redisCluster *c;
/* Actual our actual cluster structure */
c = ecalloc(1, sizeof(redisCluster));
/* Initialize flags and settings */
c->flags = ecalloc(1, sizeof(RedisSock));
c->subscribed_slot = -1;
c->clusterdown = 0;
c->timeout = timeout;
c->read_timeout = read_timeout;
c->failover = failover;
c->persistent = persistent;
c->err = NULL;
/* Set up our waitms based on timeout */
c->waitms = (long)(1000 * timeout);
/* Allocate our seeds hash table */
ALLOC_HASHTABLE(c->seeds);
zend_hash_init(c->seeds, 0, NULL, ht_free_seed, 0);
/* Allocate our nodes HashTable */
ALLOC_HASHTABLE(c->nodes);
zend_hash_init(c->nodes, 0, NULL, ht_free_node, 0);
return c;
}
PHP_REDIS_API void cluster_free(redisCluster *c) {
/* Free any allocated prefix */
if (c->flags->prefix) efree(c->flags->prefix);
efree(c->flags);
/* Call hash table destructors */
zend_hash_destroy(c->seeds);
zend_hash_destroy(c->nodes);
/* Free hash tables themselves */
efree(c->seeds);
efree(c->nodes);
/* Free any error we've got */
if (c->err) zend_string_release(c->err);
/* Free structure itself */
efree(c);
}
/* Takes our input hash table and returns a straigt C array with elements,
* which have been randomized. The return value needs to be freed. */
static zval **cluster_shuffle_seeds(HashTable *seeds, int *len) {
zval **z_seeds, *z_ele;
int *map, i, count, index=0;
/* How many */
count = zend_hash_num_elements(seeds);
/* Allocate our return value and map */
z_seeds = ecalloc(count, sizeof(zval*));
map = emalloc(sizeof(int)*count);
/* Fill in and shuffle our map */
for (i = 0; i < count; i++) map[i] = i;
fyshuffle(map, count);
/* Iterate over our source array and use our map to create a random list */
ZEND_HASH_FOREACH_VAL(seeds, z_ele) {
z_seeds[map[index++]] = z_ele;
} ZEND_HASH_FOREACH_END();
efree(map);
*len = count;
return z_seeds;
}
/* Initialize seeds */
PHP_REDIS_API int
cluster_init_seeds(redisCluster *cluster, HashTable *ht_seeds) {
RedisSock *redis_sock;
char *str, *psep, key[1024];
int key_len, count, i;
zval **z_seeds, *z_seed;
/* Get our seeds in a randomized array */
z_seeds = cluster_shuffle_seeds(ht_seeds, &count);
// Iterate our seeds array
for (i = 0; i < count; i++) {
if ((z_seed = z_seeds[i]) == NULL) continue;
ZVAL_DEREF(z_seed);
/* Has to be a string */
if (Z_TYPE_P(z_seed) != IS_STRING) continue;
// Grab a copy of the string
str = Z_STRVAL_P(z_seed);
/* Make sure we have a colon for host:port. Search right to left in the
* case of IPv6 */
if ((psep = strrchr(str, ':')) == NULL)
continue;
// Allocate a structure for this seed
redis_sock = redis_sock_create(str, psep-str,
(unsigned short)atoi(psep+1), cluster->timeout,
cluster->read_timeout, cluster->persistent, NULL, 0, 0);
// Index this seed by host/port
key_len = snprintf(key, sizeof(key), "%s:%u", ZSTR_VAL(redis_sock->host),
redis_sock->port);
// Add to our seed HashTable
zend_hash_str_update_ptr(cluster->seeds, key, key_len, redis_sock);
}
efree(z_seeds);
// Success if at least one seed seems valid
return zend_hash_num_elements(cluster->seeds) > 0 ? 0 : -1;
}
/* Initial mapping of our cluster keyspace */
PHP_REDIS_API int cluster_map_keyspace(redisCluster *c TSRMLS_DC) {
RedisSock *seed;
clusterReply *slots=NULL;
int mapped=0;
// Iterate over seeds until we can get slots
ZEND_HASH_FOREACH_PTR(c->seeds, seed) {
// Attempt to connect to this seed node
if (seed == NULL || redis_sock_connect(seed TSRMLS_CC) != 0) {
continue;
}
// Parse out cluster nodes. Flag mapped if we are valid
slots = cluster_get_slots(seed TSRMLS_CC);
if (slots) {
mapped = !cluster_map_slots(c, slots);
// Bin anything mapped, if we failed somewhere
if (!mapped) {
memset(c->master, 0, sizeof(redisClusterNode*)*REDIS_CLUSTER_SLOTS);
}
}
redis_sock_disconnect(seed TSRMLS_CC);
if (mapped) break;
} ZEND_HASH_FOREACH_END();
// Clean up slots reply if we got one
if(slots) cluster_free_reply(slots, 1);
// Throw an exception if we couldn't map
if(!mapped) {
zend_throw_exception(redis_cluster_exception_ce,
"Couldn't map cluster keyspace using any provided seed", 0
TSRMLS_CC);
return -1;
}
return 0;
}
/* Parse the MOVED OR ASK redirection payload when we get such a response
* and apply this information to our cluster. If we encounter a parse error
* nothing in the cluster will be modified, and -1 is returned. */
static int cluster_set_redirection(redisCluster* c, char *msg, int moved)
{
char *host, *port;
/* Move past "MOVED" or "ASK */
msg += moved ? MOVED_LEN : ASK_LEN;
/* Make sure we can find host */
if ((host = strchr(msg, ' ')) == NULL) return -1;
*host++ = '\0';
/* Find port, searching right to left in case of IPv6 */
if ((port = strrchr(host, ':')) == NULL) return -1;
*port++ = '\0';
// Success, apply it
c->redir_type = moved ? REDIR_MOVED : REDIR_ASK;
strncpy(c->redir_host, host, sizeof(c->redir_host) - 1);
c->redir_host_len = port - host - 1;
c->redir_slot = (unsigned short)atoi(msg);
c->redir_port = (unsigned short)atoi(port);
return 0;
}
/* Once we write a command to a node in our cluster, this function will check
* the reply type and extract information from those that will specify a length
* bit. If we encounter an error condition, we'll check for MOVED or ASK
* redirection, parsing out slot host and port so the caller can take
* appropriate action.
*
* In the case of a non MOVED/ASK error, we wlll set our cluster error
* condition so GetLastError can be queried by the client.
*
* This function will return -1 on a critical error (e.g. parse/communication
* error, 0 if no redirection was encountered, and 1 if the data was moved. */
static int cluster_check_response(redisCluster *c, REDIS_REPLY_TYPE *reply_type
TSRMLS_DC)
{
size_t sz;
// Clear out any prior error state and our last line response
CLUSTER_CLEAR_ERROR(c);
CLUSTER_CLEAR_REPLY(c);
if(-1 == redis_check_eof(c->cmd_sock, 1 TSRMLS_CC) ||
EOF == (*reply_type = php_stream_getc(c->cmd_sock->stream)))
{
return -1;
}
// In the event of an ERROR, check if it's a MOVED/ASK error
if(*reply_type == TYPE_ERR) {
char inbuf[4096];
int moved;
// Attempt to read the error
if(!php_stream_gets(c->cmd_sock->stream, inbuf, sizeof(inbuf))) {
return -1;
}
// Check for MOVED or ASK redirection
if((moved = IS_MOVED(inbuf)) || IS_ASK(inbuf)) {
// Set our redirection information
if(cluster_set_redirection(c,inbuf,moved)<0) {
return -1;
}
// Data moved
return 1;
} else {
// Capture the error string Redis returned
cluster_set_err(c, inbuf, strlen(inbuf)-2);
return 0;
}
}
// Fetch the first line of our response from Redis.
if(redis_sock_gets(c->cmd_sock,c->line_reply,sizeof(c->line_reply),
&sz TSRMLS_CC)<0)
{
return -1;
}
// For replies that will give us a numberic length, convert it
if(*reply_type != TYPE_LINE) {
c->reply_len = strtol(c->line_reply, NULL, 10);
} else {
c->reply_len = (long long)sz;
}
// Clear out any previous error, and return that the data is here
CLUSTER_CLEAR_ERROR(c);
return 0;
}
/* Disconnect from each node we're connected to */
PHP_REDIS_API void cluster_disconnect(redisCluster *c TSRMLS_DC) {
redisClusterNode *node;
ZEND_HASH_FOREACH_PTR(c->nodes, node) {
if (node == NULL) break;
redis_sock_disconnect(node->sock TSRMLS_CC);
node->sock->lazy_connect = 1;
} ZEND_HASH_FOREACH_END();
}
/* This method attempts to write our command at random to the master and any
* attached slaves, until we either successufly do so, or fail. */
static int cluster_dist_write(redisCluster *c, const char *cmd, size_t sz,
int nomaster TSRMLS_DC)
{
int i, count=1, *nodes;
RedisSock *redis_sock;
/* Determine our overall node count */
if (c->master[c->cmd_slot]->slaves) {
count += zend_hash_num_elements(c->master[c->cmd_slot]->slaves);
}
/* Allocate memory for master + slaves or just slaves */
nodes = emalloc(sizeof(int)*count);
/* Populate our array with the master and each of it's slaves, then
* randomize them, so we will pick from the master or some slave. */
for (i = 0; i < count; i++) nodes[i] = i;
fyshuffle(nodes, count);
/* Iterate through our nodes until we find one we can write to or fail */
for (i = 0; i < count; i++) {
/* Skip if this is the master node and we don't want to query that */
if (nomaster && nodes[i] == 0)
continue;
/* Get the slave for this index */
redis_sock = cluster_slot_sock(c, c->cmd_slot, nodes[i]);
if (!redis_sock) continue;
/* Connect to this node if we haven't already */
CLUSTER_LAZY_CONNECT(redis_sock);
/* If we're not on the master, attempt to send the READONLY command to
* this slave, and skip it if that fails */
if (nodes[i] == 0 || redis_sock->readonly ||
cluster_send_readonly(redis_sock TSRMLS_CC) == 0)
{
/* Attempt to send the command */
if (CLUSTER_SEND_PAYLOAD(redis_sock, cmd, sz)) {
c->cmd_sock = redis_sock;
efree(nodes);
return 0;
}
}
}
/* Clean up our shuffled array */
efree(nodes);
/* Couldn't send to the master or any slave */
return -1;
}
/* Attempt to write our command to the current c->cmd_sock socket. For write
* commands, we attempt to query the master for this slot, and in the event of
* a failure, try to query every remaining node for a redirection.
*
* If we're issuing a readonly command, we use one of three strategies, depending
* on our redisCluster->failover setting.
*
* REDIS_FAILOVER_NONE:
* The command is treated just like a write command, and will only be executed
* against the known master for this slot.
* REDIS_FAILOVER_ERROR:
* If we're unable to communicate with this slot's master, we attempt the query
* against any slaves (at random) that this master has.
* REDIS_FAILOVER_DISTRIBUTE:
* We pick at random from the master and any slaves it has. This option will
* load balance between masters and slaves
* REDIS_FAILOVER_DISTRIBUTE_SLAVES:
* We pick at random from slave nodes of a given master. This option is
* used to load balance read queries against N slaves.
*
* Once we are able to find a node we can write to, we check for MOVED or
* ASKING redirection, such that the keyspace can be updated.
*/
static int cluster_sock_write(redisCluster *c, const char *cmd, size_t sz,
int direct TSRMLS_DC)
{
redisClusterNode *seed_node;
RedisSock *redis_sock;
int failover, nomaster;
/* First try the socket requested */
redis_sock = c->cmd_sock;
/* Readonly is irrelevant if we're not configured to failover */
failover = c->readonly && c->failover != REDIS_FAILOVER_NONE ?
c->failover : REDIS_FAILOVER_NONE;
/* If in ASK redirection, get/create the node for that host:port, otherwise
* just use the command socket. */
if(c->redir_type == REDIR_ASK) {
redis_sock = cluster_get_asking_sock(c TSRMLS_CC);
if(cluster_send_asking(redis_sock TSRMLS_CC)<0) {
return -1;
}
}
/* Attempt to send our command payload to the cluster. If we're not set up
* to failover, just try the master. If we're configured to failover on
* error, try the master and then fall back to any slaves. When we're set
* up to distribute the commands, try to write to any node on this slot
* at random. */
if (failover == REDIS_FAILOVER_NONE) {
/* Success if we can send our payload to the master */
CLUSTER_LAZY_CONNECT(redis_sock);
if (CLUSTER_SEND_PAYLOAD(redis_sock, cmd, sz)) return 0;
} else if (failover == REDIS_FAILOVER_ERROR) {
/* Try the master, then fall back to any slaves we may have */
CLUSTER_LAZY_CONNECT(redis_sock);
if (CLUSTER_SEND_PAYLOAD(redis_sock, cmd, sz) ||
!cluster_dist_write(c, cmd, sz, 1 TSRMLS_CC)) return 0;
} else {
/* Include or exclude master node depending on failover option and
* attempt to make our write */
nomaster = failover == REDIS_FAILOVER_DISTRIBUTE_SLAVES;
if (!cluster_dist_write(c, cmd, sz, nomaster TSRMLS_CC)) {
/* We were able to write to a master or slave at random */
return 0;
}
}
/* Don't fall back if direct communication with this slot is required. */
if(direct) return -1;
/* Fall back by attempting the request against every known node */
ZEND_HASH_FOREACH_PTR(c->nodes, seed_node) {
/* Skip this node if it's the one that failed, or if it's a slave */
if (seed_node == NULL || seed_node->sock == redis_sock || seed_node->slave) continue;
/* Connect to this node if we haven't already */
CLUSTER_LAZY_CONNECT(seed_node->sock);
/* Attempt to write our request to this node */
if (CLUSTER_SEND_PAYLOAD(seed_node->sock, cmd, sz)) {
c->cmd_slot = seed_node->slot;
c->cmd_sock = seed_node->sock;
return 0;
}
} ZEND_HASH_FOREACH_END();
/* We were unable to write to any node in our cluster */
return -1;
}
/* Helper to find if we've got a host:port mapped in our cluster nodes. */
static redisClusterNode *cluster_find_node(redisCluster *c, const char *host,
unsigned short port)
{
int key_len;
char key[1024];
key_len = snprintf(key,sizeof(key),"%s:%d", host, port);
return zend_hash_str_find_ptr(c->nodes, key, key_len);
}
/* Provided a redisCluster object, the slot where we thought data was and
* the slot where data was moved, update our node mapping */
static void cluster_update_slot(redisCluster *c TSRMLS_DC) {
redisClusterNode *node;
char key[1024];
size_t klen;
/* Do we already have the new slot mapped */
if(c->master[c->redir_slot]) {
/* No need to do anything if it's the same node */
if(!CLUSTER_REDIR_CMP(c)) {
return;
}
/* Check to see if we have this new node mapped */
node = cluster_find_node(c, c->redir_host, c->redir_port);
if(node) {
/* Just point to this slot */
c->master[c->redir_slot] = node;
} else {
/* Create our node */
node = cluster_node_create(c, c->redir_host, c->redir_host_len,
c->redir_port, c->redir_slot, 0);
/* Our node is new, so keep track of it for cleanup */
klen = snprintf(key,sizeof(key),"%s:%ld",c->redir_host,c->redir_port);
zend_hash_str_update_ptr(c->nodes, key, klen, node);
/* Now point our slot at the node */
c->master[c->redir_slot] = node;
}
} else {
/* Check to see if the ip and port are mapped */
node = cluster_find_node(c, c->redir_host, c->redir_port);
if(!node) {
node = cluster_node_create(c, c->redir_host, c->redir_host_len,
c->redir_port, c->redir_slot, 0);
}
/* Map the slot to this node */
c->master[c->redir_slot] = node;
}
/* Update slot inside of node, so it can be found for command sending */
node->slot = c->redir_slot;
/* Make sure we unflag this node as a slave, as Redis Cluster will only ever
* direct us to master nodes. */
node->slave = 0;
}
/* Abort any transaction in process, by sending DISCARD to any nodes that
* have active transactions in progress. If we can't send DISCARD, we need
* to disconnect as it would leave us in an undefined state. */
PHP_REDIS_API int cluster_abort_exec(redisCluster *c TSRMLS_DC) {
clusterFoldItem *fi = c->multi_head;
/* Loop through our fold items */
while(fi) {
if(SLOT_SOCK(c,fi->slot)->mode == MULTI) {
if(cluster_send_discard(c, fi->slot TSRMLS_CC)<0) {
cluster_disconnect(c TSRMLS_CC);
return -1;
}
SLOT_SOCK(c,fi->slot)->mode = ATOMIC;
SLOT_SOCK(c,fi->slot)->watching = 0;
}
fi = fi->next;
}
/* Update our overall cluster state */
c->flags->mode = ATOMIC;
/* Success */
return 0;
}
/* Iterate through our slots, looking for the host/port in question. This
* should perform well enough as in almost all situations, a few or a few
* dozen servers will map all the slots */
PHP_REDIS_API short cluster_find_slot(redisCluster *c, const char *host,
unsigned short port)
{
int i;
for(i=0;imaster[i] && c->master[i]->sock &&
c->master[i]->sock->port == port &&
!strcasecmp(ZSTR_VAL(c->master[i]->sock->host), host))
{
return i;
}
}
// We didn't find it
return -1;
}
/* Send a command to a specific slot */
PHP_REDIS_API int cluster_send_slot(redisCluster *c, short slot, char *cmd,
int cmd_len, REDIS_REPLY_TYPE rtype TSRMLS_DC)
{
/* Point our cluster to this slot and it's socket */
c->cmd_slot = slot;
c->cmd_sock = SLOT_SOCK(c, slot);
/* Enable multi mode on this slot if we've been directed to but haven't
* send it to this node yet */
if (c->flags->mode == MULTI && c->cmd_sock->mode != MULTI) {
if (cluster_send_multi(c, slot TSRMLS_CC) == -1) {
zend_throw_exception(redis_cluster_exception_ce,
"Unable to enter MULTI mode on requested slot",
0 TSRMLS_CC);
return -1;
}
}
/* Try the slot */
if(cluster_sock_write(c, cmd, cmd_len, 1 TSRMLS_CC)==-1) {
return -1;
}
/* Check our response */
if(cluster_check_response(c, &c->reply_type TSRMLS_CC)!=0 ||
(rtype != TYPE_EOF && rtype != c->reply_type)) return -1;
/* Success */
return 0;
}
/* Send a command to given slot in our cluster. If we get a MOVED or ASK error
* we attempt to send the command to the node as directed. */
PHP_REDIS_API short cluster_send_command(redisCluster *c, short slot, const char *cmd,
int cmd_len TSRMLS_DC)
{
int resp, timedout=0;
long msstart;
/* Set the slot we're operating against as well as it's socket. These can
* change during our request loop if we have a master failure and are
* configured to fall back to slave nodes, or if we have to fall back to
* a different slot due to no nodes serving this slot being reachable. */
c->cmd_slot = slot;
c->cmd_sock = SLOT_SOCK(c, slot);
/* Get the current time in milliseconds to handle any timeout */
msstart = mstime();
/* Our main cluster request/reply loop. This loop runs until we're able to
* get a valid reply from a node, hit our "request" timeout, or enounter a
* CLUSTERDOWN state from Redis Cluster. */
do {
/* Send MULTI to the socket if we're in MULTI mode but haven't yet */
if (c->flags->mode == MULTI && CMD_SOCK(c)->mode != MULTI) {
/* We have to fail if we can't send MULTI to the node */
if (cluster_send_multi(c, slot TSRMLS_CC) == -1) {
zend_throw_exception(redis_cluster_exception_ce,
"Unable to enter MULTI mode on requested slot",
0 TSRMLS_CC);
return -1;
}
}
/* Attempt to deliver our command to the node, and that failing, to any
* node until we find one that is available. */
if (cluster_sock_write(c, cmd, cmd_len, 0 TSRMLS_CC) == -1) {
/* We have to abort, as no nodes are reachable */
zend_throw_exception(redis_cluster_exception_ce,
"Can't communicate with any node in the cluster",
0 TSRMLS_CC);
return -1;
}
/* Now check the response from the node we queried. */
resp = cluster_check_response(c, &c->reply_type TSRMLS_CC);
/* Handle MOVED or ASKING redirection */
if (resp == 1) {
/* Abort if we're in a transaction as it will be invalid */
if (c->flags->mode == MULTI) {
zend_throw_exception(redis_cluster_exception_ce,
"Can't process MULTI sequence when cluster is resharding",
0 TSRMLS_CC);
return -1;
}
/* Update mapping if the data has MOVED */
if (c->redir_type == REDIR_MOVED) {
cluster_update_slot(c TSRMLS_CC);
c->cmd_sock = SLOT_SOCK(c, slot);
}
}
/* Figure out if we've timed out trying to read or write the data */
timedout = resp && c->waitms ? mstime() - msstart >= c->waitms : 0;
} while(resp != 0 && !c->clusterdown && !timedout);
// If we've detected the cluster is down, throw an exception
if(c->clusterdown) {
zend_throw_exception(redis_cluster_exception_ce,
"The Redis Cluster is down (CLUSTERDOWN)", 0 TSRMLS_CC);
return -1;
} else if (timedout) {
zend_throw_exception(redis_cluster_exception_ce,
"Timed out attempting to find data in the correct node!", 0 TSRMLS_CC);
}
/* Clear redirection flag */
c->redir_type = REDIR_NONE;
// Success, return the slot where data exists.
return 0;
}
/* RedisCluster response handlers. These methods all have the same prototype
* and set the proper return value for the calling cluster method. These
* methods will never be called in the case of a communication error when
* we try to send the request to the Cluster *or* if a non MOVED or ASK
* error is encountered, in which case our response processing macro will
* short circuit and RETURN_FALSE, as the error will have already been
* consumed. */
/* RAW bulk response handler */
PHP_REDIS_API void cluster_bulk_raw_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, void *ctx)
{
char *resp;
// Make sure we can read the response
if(c->reply_type != TYPE_BULK ||
(resp = redis_sock_read_bulk_reply(c->cmd_sock, c->reply_len TSRMLS_CC))==NULL)
{
if(c->flags->mode != MULTI) {
RETURN_FALSE;
} else {
add_next_index_bool(&c->multi_resp, 0);
return;
}
}
// Return our response raw
CLUSTER_RETURN_STRING(c, resp, c->reply_len);
efree(resp);
}
/* BULK response handler */
PHP_REDIS_API void cluster_bulk_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx)
{
char *resp;
// Make sure we can read the response
if(c->reply_type != TYPE_BULK ||
(resp = redis_sock_read_bulk_reply(c->cmd_sock, c->reply_len TSRMLS_CC))==NULL)
{
CLUSTER_RETURN_FALSE(c);
}
if (CLUSTER_IS_ATOMIC(c)) {
if (!redis_unserialize(c->flags, resp, c->reply_len, return_value TSRMLS_CC)) {
CLUSTER_RETURN_STRING(c, resp, c->reply_len);
}
} else {
zval zv, *z = &zv;
if (redis_unserialize(c->flags, resp, c->reply_len, z TSRMLS_CC)) {
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z);
*z = zv;
#endif
add_next_index_zval(&c->multi_resp, z);
} else {
add_next_index_stringl(&c->multi_resp, resp, c->reply_len);
}
}
efree(resp);
}
/* Bulk response where we expect a double */
PHP_REDIS_API void cluster_dbl_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx)
{
char *resp;
double dbl;
// Make sure we can read the response
if(c->reply_type != TYPE_BULK ||
(resp = redis_sock_read_bulk_reply(c->cmd_sock, c->reply_len TSRMLS_CC))==NULL)
{
CLUSTER_RETURN_FALSE(c);
}
// Convert to double, free response
dbl = atof(resp);
efree(resp);
CLUSTER_RETURN_DOUBLE(c, dbl);
}
/* A boolean response. If we get here, we've consumed the '+' reply
* type and will now just verify we can read the OK */
PHP_REDIS_API void cluster_bool_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx)
{
// Check that we have +OK
if(c->reply_type != TYPE_LINE || c->reply_len != 2 ||
c->line_reply[0] != 'O' || c->line_reply[1] != 'K')
{
CLUSTER_RETURN_FALSE(c);
}
CLUSTER_RETURN_BOOL(c, 1);
}
/* Boolean response, specialized for PING */
PHP_REDIS_API void cluster_ping_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx)
{
if(c->reply_type != TYPE_LINE || c->reply_len != 4 ||
memcmp(c->line_reply,"PONG",sizeof("PONG")-1))
{
CLUSTER_RETURN_FALSE(c);
}
CLUSTER_RETURN_BOOL(c, 1);
}
/* 1 or 0 response, for things like SETNX */
PHP_REDIS_API void cluster_1_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx)
{
// Validate our reply type, and check for a zero
if(c->reply_type != TYPE_INT || c->reply_len == 0) {
CLUSTER_RETURN_FALSE(c);
}
CLUSTER_RETURN_BOOL(c, 1);
}
/* Generic integer response */
PHP_REDIS_API void cluster_long_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx)
{
if(c->reply_type != TYPE_INT) {
CLUSTER_RETURN_FALSE(c);
}
CLUSTER_RETURN_LONG(c, c->reply_len);
}
/* TYPE response handler */
PHP_REDIS_API void cluster_type_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx)
{
// Make sure we got the right kind of response
if(c->reply_type != TYPE_LINE) {
CLUSTER_RETURN_FALSE(c);
}
// Switch on the type
if(strncmp (c->line_reply, "string", 6)==0) {
CLUSTER_RETURN_LONG(c, REDIS_STRING);
} else if (strncmp(c->line_reply, "set", 3)==0) {
CLUSTER_RETURN_LONG(c, REDIS_SET);
} else if (strncmp(c->line_reply, "list", 4)==0) {
CLUSTER_RETURN_LONG(c, REDIS_LIST);
} else if (strncmp(c->line_reply, "hash", 4)==0) {
CLUSTER_RETURN_LONG(c, REDIS_HASH);
} else if (strncmp(c->line_reply, "zset", 4)==0) {
CLUSTER_RETURN_LONG(c, REDIS_ZSET);
} else {
CLUSTER_RETURN_LONG(c, REDIS_NOT_FOUND);
}
}
/* SUBSCRIBE/PSCUBSCRIBE handler */
PHP_REDIS_API void cluster_sub_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx)
{
subscribeContext *sctx = (subscribeContext*)ctx;
zval z_tab, *z_tmp;
int pull=0;
// Consume each MULTI BULK response (one per channel/pattern)
while(sctx->argc--) {
if (!cluster_zval_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c,
pull, mbulk_resp_loop_raw, &z_tab)
) {
efree(sctx);
RETURN_FALSE;
}
if ((z_tmp = zend_hash_index_find(Z_ARRVAL(z_tab), 0)) == NULL ||
strcasecmp(Z_STRVAL_P(z_tmp), sctx->kw) != 0
) {
zval_dtor(&z_tab);
efree(sctx);
RETURN_FALSE;
}
zval_dtor(&z_tab);
pull = 1;
}
// Set up our callback pointers
#if (PHP_MAJOR_VERSION < 7)
zval *z_ret, **z_args[4];
sctx->cb.retval_ptr_ptr = &z_ret;
#else
zval z_ret, z_args[4];
sctx->cb.retval = &z_ret;
#endif
sctx->cb.params = z_args;
sctx->cb.no_separation = 0;
/* We're in a subscribe loop */
c->subscribed_slot = c->cmd_slot;
/* Multibulk response, {[pattern], type, channel, payload} */
while(1) {
/* Arguments */
zval *z_type, *z_chan, *z_pat = NULL, *z_data;
int tab_idx=1, is_pmsg;
// Get the next subscribe response
if (!cluster_zval_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, 1, mbulk_resp_loop, &z_tab) ||
(z_type = zend_hash_index_find(Z_ARRVAL(z_tab), 0)) == NULL
) {
break;
}
// Make sure we have a message or pmessage
if (!strncmp(Z_STRVAL_P(z_type), "message", 7) ||
!strncmp(Z_STRVAL_P(z_type), "pmessage", 8)
) {
is_pmsg = *Z_STRVAL_P(z_type) == 'p';
} else {
zval_dtor(&z_tab);
continue;
}
if (is_pmsg && (z_pat = zend_hash_index_find(Z_ARRVAL(z_tab), tab_idx++)) == NULL) {
break;
}
// Extract channel and data
if ((z_chan = zend_hash_index_find(Z_ARRVAL(z_tab), tab_idx++)) == NULL ||
(z_data = zend_hash_index_find(Z_ARRVAL(z_tab), tab_idx++)) == NULL
) {
break;
}
// Always pass our object through
#if (PHP_MAJOR_VERSION < 7)
z_args[0] = &getThis();
// Set up calbacks depending on type
if(is_pmsg) {
z_args[1] = &z_pat;
z_args[2] = &z_chan;
z_args[3] = &z_data;
} else {
z_args[1] = &z_chan;
z_args[2] = &z_data;
}
#else
z_args[0] = *getThis();
// Set up calbacks depending on type
if(is_pmsg) {
z_args[1] = *z_pat;
z_args[2] = *z_chan;
z_args[3] = *z_data;
} else {
z_args[1] = *z_chan;
z_args[2] = *z_data;
}
#endif
// Set arg count
sctx->cb.param_count = tab_idx;
// Execute our callback
if(zend_call_function(&(sctx->cb), &(sctx->cb_cache) TSRMLS_CC)!=
SUCCESS)
{
break;
}
// If we have a return value, free it
zval_ptr_dtor(&z_ret);
zval_dtor(&z_tab);
}
// We're no longer subscribing, due to an error
c->subscribed_slot = -1;
// Cleanup
zval_dtor(&z_tab);
efree(sctx);
// Failure
RETURN_FALSE;
}
/* UNSUBSCRIBE/PUNSUBSCRIBE */
PHP_REDIS_API void cluster_unsub_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, void *ctx)
{
subscribeContext *sctx = (subscribeContext*)ctx;
zval z_tab, *z_chan, *z_flag;
int pull = 0, argc = sctx->argc;
efree(sctx);
array_init(return_value);
// Consume each response
while(argc--) {
// Fail if we didn't get an array or can't find index 1
if (!cluster_zval_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, pull, mbulk_resp_loop_raw, &z_tab) ||
(z_chan = zend_hash_index_find(Z_ARRVAL(z_tab), 1)) == NULL
) {
zval_dtor(&z_tab);
zval_dtor(return_value);
RETURN_FALSE;
}
// Find the flag for this channel/pattern
if ((z_flag = zend_hash_index_find(Z_ARRVAL(z_tab), 2)) == NULL ||
Z_STRLEN_P(z_flag) != 2
) {
zval_dtor(&z_tab);
zval_dtor(return_value);
RETURN_FALSE;
}
// Redis will give us either :1 or :0 here
char *flag = Z_STRVAL_P(z_flag);
// Add result
add_assoc_bool(return_value, Z_STRVAL_P(z_chan), flag[1]=='1');
zval_dtor(&z_tab);
pull = 1;
}
}
/* Recursive MULTI BULK -> PHP style response handling */
static void cluster_mbulk_variant_resp(clusterReply *r, zval *z_ret)
{
zval zv, *z_sub_ele = &zv;
int i;
switch(r->type) {
case TYPE_INT:
add_next_index_long(z_ret, r->integer);
break;
case TYPE_LINE:
add_next_index_bool(z_ret, 1);
break;
case TYPE_BULK:
if (r->len > -1) {
add_next_index_stringl(z_ret, r->str, r->len);
efree(r->str);
} else {
add_next_index_null(z_ret);
}
break;
case TYPE_MULTIBULK:
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_sub_ele);
#endif
array_init(z_sub_ele);
for(i=0;ielements;i++) {
cluster_mbulk_variant_resp(r->element[i], z_sub_ele);
}
add_next_index_zval(z_ret, z_sub_ele);
break;
default:
add_next_index_bool(z_ret, 0);
break;
}
}
/* Variant response handling, for things like EVAL and various other responses
* where we just map the replies from Redis type values to PHP ones directly. */
PHP_REDIS_API void cluster_variant_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx)
{
clusterReply *r;
zval zv, *z_arr = &zv;
int i;
// Make sure we can read it
if((r = cluster_read_resp(c TSRMLS_CC))==NULL) {
CLUSTER_RETURN_FALSE(c);
}
// Handle ATOMIC vs. MULTI mode in a seperate switch
if(CLUSTER_IS_ATOMIC(c)) {
switch(r->type) {
case TYPE_INT:
RETVAL_LONG(r->integer);
break;
case TYPE_ERR:
RETVAL_FALSE;
break;
case TYPE_LINE:
RETVAL_TRUE;
break;
case TYPE_BULK:
if (r->len < 0) {
RETVAL_NULL();
} else {
RETVAL_STRINGL(r->str, r->len);
}
break;
case TYPE_MULTIBULK:
array_init(z_arr);
for(i=0;ielements;i++) {
cluster_mbulk_variant_resp(r->element[i], z_arr);
}
RETVAL_ZVAL(z_arr, 1, 0);
break;
default:
RETVAL_FALSE;
break;
}
} else {
switch(r->type) {
case TYPE_INT:
add_next_index_long(&c->multi_resp, r->integer);
break;
case TYPE_ERR:
add_next_index_bool(&c->multi_resp, 0);
break;
case TYPE_LINE:
add_next_index_bool(&c->multi_resp, 1);
break;
case TYPE_BULK:
if (r->len < 0) {
add_next_index_null(&c->multi_resp);
} else {
add_next_index_stringl(&c->multi_resp, r->str, r->len);
efree(r->str);
}
break;
case TYPE_MULTIBULK:
cluster_mbulk_variant_resp(r, &c->multi_resp);
break;
default:
add_next_index_bool(&c->multi_resp, 0);
break;
}
}
// Free our response structs, but not allocated data itself
cluster_free_reply(r, 0);
}
/* Generic MULTI BULK response processor */
PHP_REDIS_API void cluster_gen_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, mbulk_cb cb, void *ctx)
{
zval zv, *z_result = &zv;
/* Return FALSE if we didn't get a multi-bulk response */
if (c->reply_type != TYPE_MULTIBULK) {
CLUSTER_RETURN_FALSE(c);
}
/* Allocate our array */
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_result);
#endif
array_init(z_result);
/* Consume replies as long as there are more than zero */
if (c->reply_len > 0) {
/* Push serialization settings from the cluster into our socket */
c->cmd_sock->serializer = c->flags->serializer;
/* Call our specified callback */
if (cb(c->cmd_sock, z_result, c->reply_len, ctx TSRMLS_CC)==FAILURE) {
zval_dtor(z_result);
#if (PHP_MAJOR_VERSION < 7)
efree(z_result);
#endif
CLUSTER_RETURN_FALSE(c);
}
}
// Success, make this array our return value
if(CLUSTER_IS_ATOMIC(c)) {
RETVAL_ZVAL(z_result, 0, 1);
} else {
add_next_index_zval(&c->multi_resp, z_result);
}
}
/* HSCAN, SSCAN, ZSCAN */
PHP_REDIS_API int cluster_scan_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
REDIS_SCAN_TYPE type, long *it)
{
char *pit;
// We always want to see a MULTIBULK response with two elements
if(c->reply_type != TYPE_MULTIBULK || c->reply_len != 2)
{
return FAILURE;
}
// Read the BULK size
if(cluster_check_response(c, &c->reply_type TSRMLS_CC),0 ||
c->reply_type != TYPE_BULK)
{
return FAILURE;
}
// Read the iterator
if((pit = redis_sock_read_bulk_reply(c->cmd_sock,c->reply_len TSRMLS_CC))==NULL)
{
return FAILURE;
}
// Push the new iterator value to our caller
*it = atol(pit);
efree(pit);
// We'll need another MULTIBULK response for the payload
if(cluster_check_response(c, &c->reply_type TSRMLS_CC)<0)
{
return FAILURE;
}
// Use the proper response callback depending on scan type
switch(type) {
case TYPE_SCAN:
cluster_mbulk_raw_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU,c,NULL);
break;
case TYPE_SSCAN:
cluster_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU,c,NULL);
break;
case TYPE_HSCAN:
cluster_mbulk_zipstr_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU,c,NULL);
break;
case TYPE_ZSCAN:
cluster_mbulk_zipdbl_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU,c,NULL);
break;
default:
return FAILURE;
}
// Success
return SUCCESS;
}
/* INFO response */
PHP_REDIS_API void cluster_info_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx)
{
zval zv, *z_result = &zv;
char *info;
// Read our bulk response
if((info = redis_sock_read_bulk_reply(c->cmd_sock, c->reply_len TSRMLS_CC))==NULL)
{
CLUSTER_RETURN_FALSE(c);
}
/* Parse response, free memory */
redis_parse_info_response(info, z_result);
efree(info);
// Return our array
if(CLUSTER_IS_ATOMIC(c)) {
RETVAL_ZVAL(z_result, 1, 0);
} else {
add_next_index_zval(&c->multi_resp, z_result);
}
}
/* CLIENT LIST response */
PHP_REDIS_API void cluster_client_list_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx)
{
char *info;
zval zv, *z_result = &zv;
/* Read the bulk response */
info = redis_sock_read_bulk_reply(c->cmd_sock, c->reply_len TSRMLS_CC);
if (info == NULL) {
CLUSTER_RETURN_FALSE(c);
}
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z_result);
#endif
/* Parse it and free the bulk string */
redis_parse_client_list_response(info, z_result);
efree(info);
if (CLUSTER_IS_ATOMIC(c)) {
RETVAL_ZVAL(z_result, 0, 1);
} else {
add_next_index_zval(&c->multi_resp, z_result);
}
}
/* MULTI BULK response loop where we might pull the next one */
PHP_REDIS_API zval *cluster_zval_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, int pull, mbulk_cb cb, zval *z_ret)
{
ZVAL_NULL(z_ret);
// Pull our next response if directed
if(pull) {
if(cluster_check_response(c, &c->reply_type TSRMLS_CC)<0)
{
return NULL;
}
}
// Validate reply type and length
if(c->reply_type != TYPE_MULTIBULK || c->reply_len == -1) {
return NULL;
}
array_init(z_ret);
// Call our callback
if(cb(c->cmd_sock, z_ret, c->reply_len, NULL TSRMLS_CC)==FAILURE) {
zval_dtor(z_ret);
return NULL;
}
return z_ret;
}
/* MULTI MULTI BULK reply (for EXEC) */
PHP_REDIS_API void cluster_multi_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, void *ctx)
{
zval *multi_resp = &c->multi_resp;
array_init(multi_resp);
clusterFoldItem *fi = c->multi_head;
while(fi) {
/* Make sure our transaction didn't fail here */
if (c->multi_len[fi->slot] > -1) {
/* Set the slot where we should look for responses. We don't allow
* failover inside a transaction, so it will be the master we have
* mapped. */
c->cmd_slot = fi->slot;
c->cmd_sock = SLOT_SOCK(c, fi->slot);
if(cluster_check_response(c, &c->reply_type TSRMLS_CC)<0) {
zval_dtor(multi_resp);
RETURN_FALSE;
}
fi->callback(INTERNAL_FUNCTION_PARAM_PASSTHRU, c, fi->ctx);
} else {
/* Just add false */
add_next_index_bool(multi_resp, 0);
}
fi = fi->next;
}
// Set our return array
zval_dtor(return_value);
RETVAL_ZVAL(multi_resp, 0, 1);
}
/* Generic handler for MGET */
PHP_REDIS_API void cluster_mbulk_mget_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, void *ctx)
{
clusterMultiCtx *mctx = (clusterMultiCtx*)ctx;
/* Protect against an invalid response type, -1 response length, and failure
* to consume the responses. */
c->cmd_sock->serializer = c->flags->serializer;
short fail = c->reply_type != TYPE_MULTIBULK || c->reply_len == -1 ||
mbulk_resp_loop(c->cmd_sock, mctx->z_multi, c->reply_len, NULL TSRMLS_CC)==FAILURE;
// If we had a failure, pad results with FALSE to indicate failure. Non
// existant keys (e.g. for MGET will come back as NULL)
if(fail) {
while(mctx->count--) {
add_next_index_bool(mctx->z_multi, 0);
}
}
// If this is the tail of our multi command, we can set our returns
if(mctx->last) {
if(CLUSTER_IS_ATOMIC(c)) {
RETVAL_ZVAL(mctx->z_multi, 0, 1);
} else {
add_next_index_zval(&c->multi_resp, mctx->z_multi);
}
}
// Clean up this context item
efree(mctx);
}
/* Handler for MSETNX */
PHP_REDIS_API void cluster_msetnx_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx)
{
clusterMultiCtx *mctx = (clusterMultiCtx*)ctx;
int real_argc = mctx->count/2;
// Protect against an invalid response type
if(c->reply_type != TYPE_INT) {
php_error_docref(0 TSRMLS_CC, E_WARNING,
"Invalid response type for MSETNX");
while(real_argc--) {
add_next_index_bool(mctx->z_multi, 0);
}
return;
}
// Response will be 1/0 per key, so the client can match them up
while(real_argc--) {
add_next_index_long(mctx->z_multi, c->reply_len);
}
// Set return value if it's our last response
if(mctx->last) {
if(CLUSTER_IS_ATOMIC(c)) {
RETVAL_ZVAL(mctx->z_multi, 0, 1);
} else {
add_next_index_zval(&c->multi_resp, mctx->z_multi);
}
}
// Free multi context
efree(mctx);
}
/* Handler for DEL */
PHP_REDIS_API void cluster_del_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx)
{
clusterMultiCtx *mctx = (clusterMultiCtx*)ctx;
// If we get an invalid reply, inform the client
if(c->reply_type != TYPE_INT) {
php_error_docref(0 TSRMLS_CC, E_WARNING,
"Invalid reply type returned for DEL command");
efree(mctx);
return;
}
// Increment by the number of keys deleted
Z_LVAL_P(mctx->z_multi) += c->reply_len;
if(mctx->last) {
if(CLUSTER_IS_ATOMIC(c)) {
ZVAL_LONG(return_value, Z_LVAL_P(mctx->z_multi));
} else {
add_next_index_long(&c->multi_resp, Z_LVAL_P(mctx->z_multi));
}
efree(mctx->z_multi);
}
efree(ctx);
}
/* Handler for MSET */
PHP_REDIS_API void cluster_mset_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx)
{
clusterMultiCtx *mctx = (clusterMultiCtx*)ctx;
// If we get an invalid reply type something very wrong has happened,
// and we have to abort.
if(c->reply_type != TYPE_LINE) {
php_error_docref(0 TSRMLS_CC, E_ERROR,
"Invalid reply type returned for MSET command");
zval_dtor(mctx->z_multi);
efree(mctx->z_multi);
efree(mctx);
RETURN_FALSE;
}
// Set our return if it's the last call
if(mctx->last) {
if(CLUSTER_IS_ATOMIC(c)) {
ZVAL_BOOL(return_value, zval_is_true(mctx->z_multi));
} else {
add_next_index_bool(&c->multi_resp, zval_is_true(mctx->z_multi));
}
efree(mctx->z_multi);
}
efree(mctx);
}
/* Raw MULTI BULK reply */
PHP_REDIS_API void
cluster_mbulk_raw_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx)
{
cluster_gen_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c,
mbulk_resp_loop_raw, NULL);
}
/* Unserialize all the things */
PHP_REDIS_API void
cluster_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c, void *ctx)
{
cluster_gen_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c,
mbulk_resp_loop, NULL);
}
/* For handling responses where we get key, value, key, value that
* we will turn into key => value, key => value. */
PHP_REDIS_API void
cluster_mbulk_zipstr_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx)
{
cluster_gen_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c,
mbulk_resp_loop_zipstr, NULL);
}
/* Handling key,value to key=>value where the values are doubles */
PHP_REDIS_API void
cluster_mbulk_zipdbl_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx)
{
cluster_gen_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c,
mbulk_resp_loop_zipdbl, NULL);
}
/* Associate multi bulk response (for HMGET really) */
PHP_REDIS_API void
cluster_mbulk_assoc_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx)
{
cluster_gen_mbulk_resp(INTERNAL_FUNCTION_PARAM_PASSTHRU, c,
mbulk_resp_loop_assoc, ctx);
}
/*
* Various MULTI BULK reply callback functions
*/
/* MULTI BULK response where we don't touch the values (e.g. KEYS) */
int mbulk_resp_loop_raw(RedisSock *redis_sock, zval *z_result,
long long count, void *ctx TSRMLS_DC)
{
char *line;
int line_len;
// Iterate over the number we have
while(count--) {
// Read the line, which should never come back null
line = redis_sock_read(redis_sock, &line_len TSRMLS_CC);
if(line == NULL) return FAILURE;
// Add to our result array
add_next_index_stringl(z_result, line, line_len);
efree(line);
}
// Success!
return SUCCESS;
}
/* MULTI BULK response where we unserialize everything */
int mbulk_resp_loop(RedisSock *redis_sock, zval *z_result,
long long count, void *ctx TSRMLS_DC)
{
char *line;
int line_len;
/* Iterate over the lines we have to process */
while(count--) {
/* Read our line */
line = redis_sock_read(redis_sock, &line_len TSRMLS_CC);
if (line != NULL) {
zval zv, *z = &zv;
if (redis_unserialize(redis_sock, line, line_len, z TSRMLS_CC)) {
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z);
*z = zv;
#endif
add_next_index_zval(z_result, z);
} else {
add_next_index_stringl(z_result, line, line_len);
}
efree(line);
} else {
if (line) efree(line);
add_next_index_bool(z_result, 0);
}
}
return SUCCESS;
}
/* MULTI BULK response where we turn key1,value1 into key1=>value1 */
int mbulk_resp_loop_zipstr(RedisSock *redis_sock, zval *z_result,
long long count, void *ctx TSRMLS_DC)
{
char *line, *key = NULL;
int line_len, key_len = 0;
long long idx = 0;
// Our count wil need to be divisible by 2
if(count % 2 != 0) {
return -1;
}
// Iterate through our elements
while(count--) {
// Grab our line, bomb out on failure
line = redis_sock_read(redis_sock, &line_len TSRMLS_CC);
if(!line) return -1;
if(idx++ % 2 == 0) {
// Save our key and length
key = line;
key_len = line_len;
} else {
/* Attempt serialization */
zval zv, *z = &zv;
if (redis_unserialize(redis_sock, line, line_len, z TSRMLS_CC)) {
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z);
*z = zv;
#endif
add_assoc_zval(z_result, key, z);
} else {
add_assoc_stringl_ex(z_result, key, key_len, line, line_len);
}
efree(line);
efree(key);
}
}
return SUCCESS;
}
/* MULTI BULK loop processor where we expect key,score key, score */
int mbulk_resp_loop_zipdbl(RedisSock *redis_sock, zval *z_result,
long long count, void *ctx TSRMLS_DC)
{
char *line, *key = NULL;
int line_len, key_len = 0;
long long idx = 0;
// Our context will need to be divisible by 2
if(count %2 != 0) {
return -1;
}
// While we have elements
while(count--) {
line = redis_sock_read(redis_sock, &line_len TSRMLS_CC);
if (line != NULL) {
if(idx++ % 2 == 0) {
key = line;
key_len = line_len;
} else {
zval zv, *z = &zv;
if (redis_unserialize(redis_sock,key,key_len, z TSRMLS_CC)) {
zend_string *zstr = zval_get_string(z);
add_assoc_double_ex(z_result, ZSTR_VAL(zstr), ZSTR_LEN(zstr), atof(line));
zend_string_release(zstr);
zval_dtor(z);
} else {
add_assoc_double_ex(z_result, key, key_len, atof(line));
}
/* Free our key and line */
efree(key);
efree(line);
}
}
}
return SUCCESS;
}
/* MULTI BULK where we're passed the keys, and we attach vals */
int mbulk_resp_loop_assoc(RedisSock *redis_sock, zval *z_result,
long long count, void *ctx TSRMLS_DC)
{
char *line;
int line_len,i=0;
zval *z_keys = ctx;
// Loop while we've got replies
while(count--) {
line = redis_sock_read(redis_sock, &line_len TSRMLS_CC);
if(line != NULL) {
zval zv, *z = &zv;
if (redis_unserialize(redis_sock, line, line_len, z TSRMLS_CC)) {
#if (PHP_MAJOR_VERSION < 7)
MAKE_STD_ZVAL(z);
*z = zv;
#endif
add_assoc_zval_ex(z_result,Z_STRVAL(z_keys[i]),
Z_STRLEN(z_keys[i]), z);
} else {
add_assoc_stringl_ex(z_result, Z_STRVAL(z_keys[i]),
Z_STRLEN(z_keys[i]), line, line_len);
}
efree(line);
} else {
add_assoc_bool_ex(z_result, Z_STRVAL(z_keys[i]),
Z_STRLEN(z_keys[i]), 0);
}
// Clean up key context
zval_dtor(&z_keys[i]);
// Move to the next key
i++;
}
// Clean up our keys overall
efree(z_keys);
// Success!
return SUCCESS;
}
/* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */
redis-3.1.6/cluster_library.h 0000644 0001750 0000012 00000040130 13223116600 016754 0 ustar pyatsukhnenko wheel #ifndef _PHPREDIS_CLUSTER_LIBRARY_H
#define _PHPREDIS_CLUSTER_LIBRARY_H
#include "common.h"
#ifdef ZTS
#include "TSRM.h"
#endif
/* Redis cluster hash slots and N-1 which we'll use to find it */
#define REDIS_CLUSTER_SLOTS 16384
#define REDIS_CLUSTER_MOD (REDIS_CLUSTER_SLOTS-1)
/* Complete representation for various commands in RESP */
#define RESP_MULTI_CMD "*1\r\n$5\r\nMULTI\r\n"
#define RESP_EXEC_CMD "*1\r\n$4\r\nEXEC\r\n"
#define RESP_DISCARD_CMD "*1\r\n$7\r\nDISCARD\r\n"
#define RESP_UNWATCH_CMD "*1\r\n$7\r\nUNWATCH\r\n"
#define RESP_CLUSTER_SLOTS_CMD "*2\r\n$7\r\nCLUSTER\r\n$5\r\nSLOTS\r\n"
#define RESP_ASKING_CMD "*1\r\n$6\r\nASKING\r\n"
#define RESP_READONLY_CMD "*1\r\n$8\r\nREADONLY\r\n"
#define RESP_READWRITE_CMD "*1\r\n$9\r\nREADWRITE\r\n"
#define RESP_READONLY_CMD_LEN (sizeof(RESP_READONLY_CMD)-1)
/* MOVED/ASK comparison macros */
#define IS_MOVED(p) (p[0]=='M' && p[1]=='O' && p[2]=='V' && p[3]=='E' && \
p[4]=='D' && p[5]==' ')
#define IS_ASK(p) (p[0]=='A' && p[1]=='S' && p[3]=='K' && p[4]==' ')
/* MOVED/ASK lengths */
#define MOVED_LEN (sizeof("MOVED ")-1)
#define ASK_LEN (sizeof("ASK ")-1)
/* Initial allocation size for key distribution container */
#define CLUSTER_KEYDIST_ALLOC 8
/* Macros to access nodes, sockets, and streams for a given slot */
#define SLOT(c,s) (c->master[s])
#define SLOT_SOCK(c,s) (SLOT(c,s)->sock)
#define SLOT_STREAM(c,s) (SLOT_SOCK(c,s)->stream)
#define SLOT_SLAVES(c,s) (c->master[s]->slaves)
/* Macros to access socket and stream for the node we're communicating with */
#define CMD_SOCK(c) (c->cmd_sock)
#define CMD_STREAM(c) (c->cmd_sock->stream)
/* Compare redirection slot information with what we have */
#define CLUSTER_REDIR_CMP(c) \
(SLOT_SOCK(c,c->redir_slot)->port != c->redir_port || \
ZSTR_LEN(SLOT_SOCK(c,c->redir_slot)->host) != c->redir_host_len || \
memcmp(ZSTR_VAL(SLOT_SOCK(c,c->redir_slot)->host),c->redir_host,c->redir_host_len))
/* Lazy connect logic */
#define CLUSTER_LAZY_CONNECT(s) \
if(s->lazy_connect) { \
s->lazy_connect = 0; \
redis_sock_server_open(s TSRMLS_CC); \
}
/* Clear out our "last error" */
#define CLUSTER_CLEAR_ERROR(c) do { \
if (c->err) { \
zend_string_release(c->err); \
c->err = NULL; \
} \
c->clusterdown = 0; \
} while (0)
/* Protected sending of data down the wire to a RedisSock->stream */
#define CLUSTER_SEND_PAYLOAD(sock, buf, len) \
(sock && sock->stream && !redis_check_eof(sock, 1 TSRMLS_CC) && \
php_stream_write(sock->stream, buf, len)==len)
/* Macro to read our reply type character */
#define CLUSTER_VALIDATE_REPLY_TYPE(sock, type) \
(redis_check_eof(sock, 1 TSRMLS_CC) == 0 && \
(php_stream_getc(sock->stream) == type))
/* Reset our last single line reply buffer and length */
#define CLUSTER_CLEAR_REPLY(c) \
*c->line_reply = '\0'; c->reply_len = 0;
/* Helper to determine if we're in MULTI mode */
#define CLUSTER_IS_ATOMIC(c) (c->flags->mode != MULTI)
/* Helper that either returns false or adds false in multi mode */
#define CLUSTER_RETURN_FALSE(c) \
if(CLUSTER_IS_ATOMIC(c)) { \
RETURN_FALSE; \
} else { \
add_next_index_bool(&c->multi_resp, 0); \
return; \
}
/* Helper to either return a bool value or add it to MULTI response */
#define CLUSTER_RETURN_BOOL(c, b) \
if(CLUSTER_IS_ATOMIC(c)) { \
if(b==1) {\
RETURN_TRUE; \
} else {\
RETURN_FALSE; \
} \
} else { \
add_next_index_bool(&c->multi_resp, b); \
}
/* Helper to respond with a double or add it to our MULTI response */
#define CLUSTER_RETURN_DOUBLE(c, d) \
if(CLUSTER_IS_ATOMIC(c)) { \
RETURN_DOUBLE(d); \
} else { \
add_next_index_double(&c->multi_resp, d); \
}
/* Helper to return a string value */
#define CLUSTER_RETURN_STRING(c, str, len) \
if(CLUSTER_IS_ATOMIC(c)) { \
RETVAL_STRINGL(str, len); \
} else { \
add_next_index_stringl(&c->multi_resp, str, len); \
} \
/* Return a LONG value */
#define CLUSTER_RETURN_LONG(c, val) \
if(CLUSTER_IS_ATOMIC(c)) { \
RETURN_LONG(val); \
} else { \
add_next_index_long(&c->multi_resp, val); \
}
/* Macro to clear out a clusterMultiCmd structure */
#define CLUSTER_MULTI_CLEAR(mc) \
mc->cmd.len = 0; \
mc->args.len = 0; \
mc->argc = 0; \
/* Initialzie a clusterMultiCmd with a keyword and length */
#define CLUSTER_MULTI_INIT(mc, keyword, keyword_len) \
mc.kw = keyword; \
mc.kw_len = keyword_len; \
/* Cluster redirection enum */
typedef enum CLUSTER_REDIR_TYPE {
REDIR_NONE,
REDIR_MOVED,
REDIR_ASK
} CLUSTER_REDIR_TYPE;
/* MULTI BULK response callback typedef */
typedef int (*mbulk_cb)(RedisSock*,zval*,long long, void* TSRMLS_DC);
/* Specific destructor to free a cluster object */
// void redis_destructor_redis_cluster(zend_resource *rsrc TSRMLS_DC);
/* A Redis Cluster master node */
typedef struct redisClusterNode {
/* Our Redis socket in question */
RedisSock *sock;
/* A slot where one of these lives */
short slot;
/* Is this a slave node */
unsigned short slave;
/* A HashTable containing any slaves */
HashTable *slaves;
} redisClusterNode;
/* Forward declarations */
typedef struct clusterFoldItem clusterFoldItem;
/* RedisCluster implementation structure */
typedef struct redisCluster {
#if (PHP_MAJOR_VERSION < 7)
zend_object std;
#endif
/* Timeout and read timeout (for normal operations) */
double timeout;
double read_timeout;
/* Are we using persistent connections */
int persistent;
/* How long in milliseconds should we wait when being bounced around */
long waitms;
/* Are we flagged as being in readonly mode, meaning we could fall back to
* a given master's slave */
short readonly;
/* RedisCluster failover options (never, on error, to load balance) */
short failover;
/* Hash table of seed host/ports */
HashTable *seeds;
/* RedisCluster masters, by direct slot */
redisClusterNode *master[REDIS_CLUSTER_SLOTS];
/* All RedisCluster objects we've created/are connected to */
HashTable *nodes;
/* Transaction handling linked list, and where we are as we EXEC */
clusterFoldItem *multi_head;
clusterFoldItem *multi_curr;
/* When we issue EXEC to nodes, we need to keep track of how many replies
* we have, as this can fail for various reasons (EXECABORT, watch, etc.) */
char multi_len[REDIS_CLUSTER_SLOTS];
/* Variable to store MULTI response */
zval multi_resp;
/* Flag for when we get a CLUSTERDOWN error */
short clusterdown;
/* The last ERROR we encountered */
zend_string *err;
/* The slot our command is operating on, as well as it's socket */
unsigned short cmd_slot;
RedisSock *cmd_sock;
/* The slot where we're subscribed */
short subscribed_slot;
/* One RedisSock struct for serialization and prefix information */
RedisSock *flags;
/* The first line of our last reply, not including our reply type byte
* or the trailing \r\n */
char line_reply[1024];
/* The last reply type and length or integer response we got */
REDIS_REPLY_TYPE reply_type;
long long reply_len;
/* Last MOVED or ASK redirection response information */
CLUSTER_REDIR_TYPE redir_type;
char redir_host[255];
int redir_host_len;
unsigned short redir_slot;
unsigned short redir_port;
#if (PHP_MAJOR_VERSION >= 7)
/* Zend object handler */
zend_object std;
#endif
} redisCluster;
/* RedisCluster response processing callback */
typedef void (*cluster_cb)(INTERNAL_FUNCTION_PARAMETERS, redisCluster*, void*);
/* Context for processing transactions */
struct clusterFoldItem {
/* Response processing callback */
cluster_cb callback;
/* The actual socket where we send this request */
unsigned short slot;
/* Any context we need to send to our callback */
void *ctx;
/* Next item in our list */
struct clusterFoldItem *next;
};
/* Key and value container, with info if they need freeing */
typedef struct clusterKeyVal {
char *key, *val;
int key_len, val_len;
int key_free, val_free;
} clusterKeyVal;
/* Container to hold keys (and possibly values) for when we need to distribute
* commands across more than 1 node (e.g. WATCH, MGET, MSET, etc) */
typedef struct clusterDistList {
clusterKeyVal *entry;
size_t len, size;
} clusterDistList;
/* Context for things like MGET/MSET/MSETNX. When executing in MULTI mode,
* we'll want to re-integrate into one running array, except for the last
* command execution, in which we'll want to return the value (or add it) */
typedef struct clusterMultiCtx {
/* Our running array */
zval *z_multi;
/* How many keys did we request for this bit */
int count;
/* Is this the last entry */
short last;
} clusterMultiCtx;
/* Container for things like MGET, MSET, and MSETNX, which split the command
* into a header and payload while aggregating to a specific slot. */
typedef struct clusterMultiCmd {
/* Keyword and keyword length */
char *kw;
int kw_len;
/* Arguments in our payload */
int argc;
/* The full command, built into cmd, and args as we aggregate */
smart_string cmd;
smart_string args;
} clusterMultiCmd;
/* Hiredis like structure for processing any sort of reply Redis Cluster might
* give us, including N level deep nested multi-bulk replies. Unlike hiredis
* we don't encode errors, here as that's handled in the cluster structure. */
typedef struct clusterReply {
REDIS_REPLY_TYPE type; /* Our reply type */
size_t integer; /* Integer reply */
long long len; /* Length of our string */
char *str; /* String reply */
size_t elements; /* Count of array elements */
struct clusterReply **element; /* Array elements */
} clusterReply;
/* Direct variant response handler */
clusterReply *cluster_read_resp(redisCluster *c TSRMLS_DC);
clusterReply *cluster_read_sock_resp(RedisSock *redis_sock,
REDIS_REPLY_TYPE type, size_t reply_len TSRMLS_DC);
void cluster_free_reply(clusterReply *reply, int free_data);
/* Cluster distribution helpers for WATCH */
HashTable *cluster_dist_create();
void cluster_dist_free(HashTable *ht);
int cluster_dist_add_key(redisCluster *c, HashTable *ht, char *key,
strlen_t key_len, clusterKeyVal **kv);
void cluster_dist_add_val(redisCluster *c, clusterKeyVal *kv, zval *val
TSRMLS_DC);
/* Aggregation for multi commands like MGET, MSET, and MSETNX */
void cluster_multi_init(clusterMultiCmd *mc, char *kw, int kw_len);
void cluster_multi_free(clusterMultiCmd *mc);
void cluster_multi_add(clusterMultiCmd *mc, char *data, int data_len);
void cluster_multi_fini(clusterMultiCmd *mc);
/* Hash a key to it's slot, using the Redis Cluster hash algorithm */
unsigned short cluster_hash_key_zval(zval *key);
unsigned short cluster_hash_key(const char *key, int len);
/* Get the current time in miliseconds */
long long mstime(void);
PHP_REDIS_API short cluster_send_command(redisCluster *c, short slot, const char *cmd,
int cmd_len TSRMLS_DC);
PHP_REDIS_API void cluster_disconnect(redisCluster *c TSRMLS_DC);
PHP_REDIS_API int cluster_send_exec(redisCluster *c, short slot TSRMLS_DC);
PHP_REDIS_API int cluster_send_discard(redisCluster *c, short slot TSRMLS_DC);
PHP_REDIS_API int cluster_abort_exec(redisCluster *c TSRMLS_DC);
PHP_REDIS_API int cluster_reset_multi(redisCluster *c);
PHP_REDIS_API short cluster_find_slot(redisCluster *c, const char *host,
unsigned short port);
PHP_REDIS_API int cluster_send_slot(redisCluster *c, short slot, char *cmd,
int cmd_len, REDIS_REPLY_TYPE rtype TSRMLS_DC);
PHP_REDIS_API redisCluster *cluster_create(double timeout, double read_timeout,
int failover, int persistent);
PHP_REDIS_API void cluster_free(redisCluster *c);
PHP_REDIS_API int cluster_init_seeds(redisCluster *c, HashTable *ht_seeds);
PHP_REDIS_API int cluster_map_keyspace(redisCluster *c TSRMLS_DC);
PHP_REDIS_API void cluster_free_node(redisClusterNode *node);
PHP_REDIS_API char **cluster_sock_read_multibulk_reply(RedisSock *redis_sock,
int *len TSRMLS_DC);
/*
* Redis Cluster response handlers. Our response handlers generally take the
* following form:
* PHP_REDIS_API void handler(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
* void *ctx)
*
* Reply handlers are responsible for setting the PHP return value (either to
* something valid, or FALSE in the case of some failures).
*/
PHP_REDIS_API void cluster_bool_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx);
PHP_REDIS_API void cluster_ping_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx);
PHP_REDIS_API void cluster_bulk_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx);
PHP_REDIS_API void cluster_bulk_raw_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx);
PHP_REDIS_API void cluster_dbl_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx);
PHP_REDIS_API void cluster_1_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx);
PHP_REDIS_API void cluster_long_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx);
PHP_REDIS_API void cluster_type_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx);
PHP_REDIS_API void cluster_sub_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx);
PHP_REDIS_API void cluster_unsub_resp(INTERNAL_FUNCTION_PARAMETERS, redisCluster *c,
void *ctx);
/* Generic/Variant handler for stuff like EVAL */
PHP_REDIS_API void cluster_variant_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, void *ctx);
/* MULTI BULK response functions */
PHP_REDIS_API void cluster_gen_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, mbulk_cb func, void *ctx);
PHP_REDIS_API void cluster_mbulk_raw_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, void *ctx);
PHP_REDIS_API void cluster_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, void *ctx);
PHP_REDIS_API void cluster_mbulk_zipstr_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, void *ctx);
PHP_REDIS_API void cluster_mbulk_zipdbl_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, void *ctx);
PHP_REDIS_API void cluster_mbulk_assoc_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, void *ctx);
PHP_REDIS_API void cluster_multi_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, void *ctx);
PHP_REDIS_API zval *cluster_zval_mbulk_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, int pull, mbulk_cb cb, zval *z_ret);
/* Handlers for things like DEL/MGET/MSET/MSETNX */
PHP_REDIS_API void cluster_del_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, void *ctx);
PHP_REDIS_API void cluster_mbulk_mget_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, void *ctx);
PHP_REDIS_API void cluster_mset_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, void *ctx);
PHP_REDIS_API void cluster_msetnx_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, void *ctx);
/* Response handler for ZSCAN, SSCAN, and HSCAN */
PHP_REDIS_API int cluster_scan_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, REDIS_SCAN_TYPE type, long *it);
/* INFO response handler */
PHP_REDIS_API void cluster_info_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, void *ctx);
/* CLIENT LIST response handler */
PHP_REDIS_API void cluster_client_list_resp(INTERNAL_FUNCTION_PARAMETERS,
redisCluster *c, void *ctx);
/* MULTI BULK processing callbacks */
int mbulk_resp_loop(RedisSock *redis_sock, zval *z_result,
long long count, void *ctx TSRMLS_DC);
int mbulk_resp_loop_raw(RedisSock *redis_sock, zval *z_result,
long long count, void *ctx TSRMLS_DC);
int mbulk_resp_loop_zipstr(RedisSock *redis_sock, zval *z_result,
long long count, void *ctx TSRMLS_DC);
int mbulk_resp_loop_zipdbl(RedisSock *redis_sock, zval *z_result,
long long count, void *ctx TSRMLS_DC);
int mbulk_resp_loop_assoc(RedisSock *redis_sock, zval *z_result,
long long count, void *ctx TSRMLS_DC);
#endif
/* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */
redis-3.1.6/redis_commands.c 0000644 0001750 0000012 00000301324 13223116600 016536 0 ustar pyatsukhnenko wheel /* -*- Mode: C; tab-width: 4 -*- */
/*
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2009 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Original Author: Michael Grunder |
+----------------------------------------------------------------------+
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "redis_commands.h"
#include
/* Local passthrough macro for command construction. Given that these methods
* are generic (so they work whether the caller is Redis or RedisCluster) we
* will always have redis_sock, slot*, and TSRMLS_CC */
#define REDIS_CMD_SPPRINTF(ret, kw, fmt, ...) \
redis_spprintf(redis_sock, slot TSRMLS_CC, ret, kw, fmt, ##__VA_ARGS__)
/* Generic commands based on method signature and what kind of things we're
* processing. Lots of Redis commands take something like key, value, or
* key, value long. Each unique signature like this is written only once */
/* A command that takes no arguments */
int redis_empty_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "");
return SUCCESS;
}
/* Helper to construct a raw command. Given that the cluster and non cluster
* versions are different (RedisCluster needs an additional argument to direct
* the command) we take the start of our array and count */
int redis_build_raw_cmd(zval *z_args, int argc, char **cmd, int *cmd_len TSRMLS_DC)
{
smart_string cmdstr = {0};
int i;
/* Make sure our first argument is a string */
if (Z_TYPE(z_args[0]) != IS_STRING) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"When sending a 'raw' command, the first argument must be a string!");
return FAILURE;
}
/* Initialize our command string */
redis_cmd_init_sstr(&cmdstr, argc-1, Z_STRVAL(z_args[0]), Z_STRLEN(z_args[0]));
for (i = 1; i < argc; i++) {
switch (Z_TYPE(z_args[i])) {
case IS_STRING:
redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[i]),
Z_STRLEN(z_args[i]));
break;
case IS_LONG:
redis_cmd_append_sstr_long(&cmdstr,Z_LVAL(z_args[i]));
break;
case IS_DOUBLE:
redis_cmd_append_sstr_dbl(&cmdstr,Z_DVAL(z_args[i]));
break;
default:
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"Raw command arguments must be scalar values!");
efree(cmdstr.c);
return FAILURE;
}
}
/* Push command and length to caller */
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* Generic command where we just take a string and do nothing to it*/
int redis_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *arg;
strlen_t arg_len;
// Parse args
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len)
==FAILURE)
{
return FAILURE;
}
// Build the command without molesting the string
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "s", arg, arg_len);
return SUCCESS;
}
/* Key, long, zval (serialized) */
int redis_key_long_val_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key = NULL;
strlen_t key_len;
zend_long expire;
zval *z_val;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "slz", &key, &key_len,
&expire, &z_val)==FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "klv", key, key_len, expire, z_val);
return SUCCESS;
}
/* Generic key, long, string (unserialized) */
int redis_key_long_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key, *val;
strlen_t key_len, val_len;
zend_long lval;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sls", &key, &key_len,
&lval, &val, &val_len)==FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kds", key, key_len, (int)lval, val, val_len);
return SUCCESS;
}
/* Generic command construction when we just take a key and value */
int redis_kv_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key;
strlen_t key_len;
zval *z_val;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &key, &key_len,
&z_val)==FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kv", key, key_len, z_val);
return SUCCESS;
}
/* Generic command that takes a key and an unserialized value */
int redis_key_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key, *val;
strlen_t key_len, val_len;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &key, &key_len,
&val, &val_len)==FAILURE)
{
return FAILURE;
}
// Construct command
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ks", key, key_len, val, val_len);
return SUCCESS;
}
/* Key, string, string without serialization (ZCOUNT, ZREMRANGEBYSCORE) */
int redis_key_str_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *k, *v1, *v2;
strlen_t klen, v1len, v2len;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss", &k, &klen,
&v1, &v1len, &v2, &v2len)==FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", k, klen, v1, v1len, v2, v2len);
// Success!
return SUCCESS;
}
/* Generic command that takes two keys */
int redis_key_key_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *k1, *k2;
strlen_t k1len, k2len;
int k1free, k2free;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &k1, &k1len,
&k2, &k2len)==FAILURE)
{
return FAILURE;
}
// Prefix both keys
k1free = redis_key_prefix(redis_sock, &k1, &k1len);
k2free = redis_key_prefix(redis_sock, &k2, &k2len);
// If a slot is requested, we can test that they hash the same
if(slot) {
// Slots where these keys resolve
short slot1 = cluster_hash_key(k1, k1len);
short slot2 = cluster_hash_key(k2, k2len);
// Check if Redis would give us a CROSSLOT error
if(slot1 != slot2) {
php_error_docref(0 TSRMLS_CC, E_WARNING, "Keys don't hash to the same slot");
if(k1free) efree(k1);
if(k2free) efree(k2);
return FAILURE;
}
// They're both the same
*slot = slot1;
}
/* Send keys as normal strings because we manually prefixed to check against
* cross slot error. */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ss", k1, k1len, k2, k2len);
/* Clean keys up if we prefixed */
if (k1free) efree(k1);
if (k2free) efree(k2);
return SUCCESS;
}
/* Generic command construction where we take a key and a long */
int redis_key_long_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key;
strlen_t keylen;
zend_long lval;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl", &key, &keylen, &lval)
==FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kl", key, keylen, lval);
// Success!
return SUCCESS;
}
/* key, long, long */
int redis_key_long_long_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key;
strlen_t key_len;
zend_long val1, val2;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sll", &key, &key_len,
&val1, &val2)==FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kll", key, key_len, val1, val2);
return SUCCESS;
}
/* Generic command where we take a single key */
int redis_key_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key;
strlen_t key_len;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_len)
==FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "k", key, key_len);
return SUCCESS;
}
/* Generic command where we take a key and a double */
int redis_key_dbl_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key;
strlen_t key_len;
double val;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sd", &key, &key_len,
&val)==FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kf", key, key_len, val);
return SUCCESS;
}
/* Generic to construct SCAN and variant commands */
int redis_fmt_scan_cmd(char **cmd, REDIS_SCAN_TYPE type, char *key, int key_len,
long it, char *pat, int pat_len, long count)
{
static char *kw[] = {"SCAN","SSCAN","HSCAN","ZSCAN"};
int argc;
smart_string cmdstr = {0};
// Figure out our argument count
argc = 1 + (type!=TYPE_SCAN) + (pat_len>0?2:0) + (count>0?2:0);
redis_cmd_init_sstr(&cmdstr, argc, kw[type], strlen(kw[type]));
// Append our key if it's not a regular SCAN command
if(type != TYPE_SCAN) {
redis_cmd_append_sstr(&cmdstr, key, key_len);
}
// Append cursor
redis_cmd_append_sstr_long(&cmdstr, it);
// Append count if we've got one
if(count) {
redis_cmd_append_sstr(&cmdstr,"COUNT",sizeof("COUNT")-1);
redis_cmd_append_sstr_long(&cmdstr, count);
}
// Append pattern if we've got one
if(pat_len) {
redis_cmd_append_sstr(&cmdstr,"MATCH",sizeof("MATCH")-1);
redis_cmd_append_sstr(&cmdstr,pat,pat_len);
}
// Push command to the caller, return length
*cmd = cmdstr.c;
return cmdstr.len;
}
/* ZRANGE/ZREVRANGE */
int redis_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, int *withscores,
short *slot, void **ctx)
{
char *key;
strlen_t key_len;
zend_long start, end;
zend_bool ws=0;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sll|b", &key, &key_len,
&start, &end, &ws)==FAILURE)
{
return FAILURE;
}
if(ws) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kdds", key, key_len, start, end,
"WITHSCORES", sizeof("WITHSCORES") - 1);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kdd", key, key_len, start, end);
}
// Push out WITHSCORES option
*withscores = ws;
return SUCCESS;
}
/* ZRANGEBYSCORE/ZREVRANGEBYSCORE */
#define IS_WITHSCORES_ARG(s, l) \
(l == sizeof("withscores") - 1 && !strncasecmp(s, "withscores", l))
#define IS_LIMIT_ARG(s, l) \
(l == sizeof("limit") - 1 && !strncasecmp(s,"limit", l))
int redis_zrangebyscore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, int *withscores,
short *slot, void **ctx)
{
char *key, *start, *end;
int has_limit=0;
long offset, count;
strlen_t key_len, start_len, end_len;
zval *z_opt=NULL, *z_ele;
zend_string *zkey;
ulong idx;
HashTable *ht_opt;
PHPREDIS_NOTUSED(idx);
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss|a", &key, &key_len,
&start, &start_len, &end, &end_len, &z_opt)
==FAILURE)
{
return FAILURE;
}
// Check for an options array
if(z_opt && Z_TYPE_P(z_opt)==IS_ARRAY) {
ht_opt = Z_ARRVAL_P(z_opt);
ZEND_HASH_FOREACH_KEY_VAL(ht_opt, idx, zkey, z_ele) {
/* All options require a string key type */
if (!zkey) continue;
ZVAL_DEREF(z_ele);
/* Check for withscores and limit */
if (IS_WITHSCORES_ARG(ZSTR_VAL(zkey), ZSTR_LEN(zkey))) {
*withscores = zval_is_true(z_ele);
} else if (IS_LIMIT_ARG(ZSTR_VAL(zkey), ZSTR_LEN(zkey)) && Z_TYPE_P(z_ele) == IS_ARRAY) {
HashTable *htlimit = Z_ARRVAL_P(z_ele);
zval *zoff, *zcnt;
/* We need two arguments (offset and count) */
if ((zoff = zend_hash_index_find(htlimit, 0)) != NULL &&
(zcnt = zend_hash_index_find(htlimit, 1)) != NULL
) {
/* Set our limit if we can get valid longs from both args */
offset = zval_get_long(zoff);
count = zval_get_long(zcnt);
has_limit = 1;
}
}
} ZEND_HASH_FOREACH_END();
}
// Construct our command
if (*withscores) {
if (has_limit) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksssdds", key, key_len,
start, start_len, end, end_len, "LIMIT", 5, offset, count,
"WITHSCORES", 10);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksss", key, key_len, start,
start_len, end, end_len, "WITHSCORES", 10);
}
} else {
if (has_limit) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksssdd", key, key_len, start,
start_len, end, end_len, "LIMIT", 5, offset, count);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", key, key_len, start,
start_len, end, end_len);
}
}
return SUCCESS;
}
/* ZUNIONSTORE, ZINTERSTORE */
int redis_zinter_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key, *agg_op=NULL;
int key_free, argc = 2, keys_count;
strlen_t key_len, agg_op_len = 0;
zval *z_keys, *z_weights=NULL, *z_ele;
HashTable *ht_keys, *ht_weights=NULL;
smart_string cmdstr = {0};
// Parse args
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa|a!s", &key,
&key_len, &z_keys, &z_weights, &agg_op,
&agg_op_len)==FAILURE)
{
return FAILURE;
}
// Grab our keys
ht_keys = Z_ARRVAL_P(z_keys);
// Nothing to do if there aren't any
if((keys_count = zend_hash_num_elements(ht_keys))==0) {
return FAILURE;
} else {
argc += keys_count;
}
// Handle WEIGHTS
if(z_weights != NULL) {
ht_weights = Z_ARRVAL_P(z_weights);
if(zend_hash_num_elements(ht_weights) != keys_count) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"WEIGHTS and keys array should be the same size!");
return FAILURE;
}
// "WEIGHTS" + key count
argc += keys_count + 1;
}
// AGGREGATE option
if(agg_op_len != 0) {
if(strncasecmp(agg_op, "SUM", sizeof("SUM")) &&
strncasecmp(agg_op, "MIN", sizeof("MIN")) &&
strncasecmp(agg_op, "MAX", sizeof("MAX")))
{
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"Invalid AGGREGATE option provided!");
return FAILURE;
}
// "AGGREGATE" + type
argc += 2;
}
// Prefix key
key_free = redis_key_prefix(redis_sock, &key, &key_len);
// Start building our command
redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
redis_cmd_append_sstr(&cmdstr, key, key_len);
redis_cmd_append_sstr_int(&cmdstr, keys_count);
// Set our slot, free the key if we prefixed it
CMD_SET_SLOT(slot,key,key_len);
if(key_free) efree(key);
// Process input keys
ZEND_HASH_FOREACH_VAL(ht_keys, z_ele) {
zend_string *zstr = zval_get_string(z_ele);
char *key = ZSTR_VAL(zstr);
strlen_t key_len = ZSTR_LEN(zstr);
// Prefix key if necissary
int key_free = redis_key_prefix(redis_sock, &key, &key_len);
// If we're in Cluster mode, verify the slot is the same
if(slot && *slot != cluster_hash_key(key,key_len)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"All keys don't hash to the same slot!");
efree(cmdstr.c);
zend_string_release(zstr);
if(key_free) efree(key);
return FAILURE;
}
// Append this input set
redis_cmd_append_sstr(&cmdstr, key, key_len);
// Cleanup
zend_string_release(zstr);
if(key_free) efree(key);
} ZEND_HASH_FOREACH_END();
// Weights
if(ht_weights != NULL) {
redis_cmd_append_sstr(&cmdstr, "WEIGHTS", sizeof("WEIGHTS")-1);
// Process our weights
ZEND_HASH_FOREACH_VAL(ht_weights, z_ele) {
// Ignore non numeric args unless they're inf/-inf
ZVAL_DEREF(z_ele);
switch (Z_TYPE_P(z_ele)) {
case IS_LONG:
redis_cmd_append_sstr_long(&cmdstr, Z_LVAL_P(z_ele));
break;
case IS_DOUBLE:
redis_cmd_append_sstr_dbl(&cmdstr, Z_DVAL_P(z_ele));
break;
case IS_STRING: {
double dval;
zend_long lval;
zend_uchar type = is_numeric_string(Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele), &lval, &dval, 0);
if (type == IS_LONG) {
redis_cmd_append_sstr_long(&cmdstr, lval);
break;
} else if (type == IS_DOUBLE) {
redis_cmd_append_sstr_dbl(&cmdstr, dval);
break;
} else if (strncasecmp(Z_STRVAL_P(z_ele), "-inf", sizeof("-inf") - 1) == 0 ||
strncasecmp(Z_STRVAL_P(z_ele), "+inf", sizeof("+inf") - 1) == 0 ||
strncasecmp(Z_STRVAL_P(z_ele), "inf", sizeof("inf") - 1) == 0
) {
redis_cmd_append_sstr(&cmdstr, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
break;
}
// fall through
}
default:
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"Weights must be numeric or '-inf','inf','+inf'");
efree(cmdstr.c);
return FAILURE;
}
} ZEND_HASH_FOREACH_END();
}
// AGGREGATE
if(agg_op_len != 0) {
redis_cmd_append_sstr(&cmdstr, "AGGREGATE", sizeof("AGGREGATE")-1);
redis_cmd_append_sstr(&cmdstr, agg_op, agg_op_len);
}
// Push out values
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* SUBSCRIBE/PSUBSCRIBE */
int redis_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
zval *z_arr, *z_chan;
HashTable *ht_chan;
smart_string cmdstr = {0};
subscribeContext *sctx = emalloc(sizeof(subscribeContext));
strlen_t key_len;
int key_free;
char *key;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "af", &z_arr,
&(sctx->cb), &(sctx->cb_cache))==FAILURE)
{
efree(sctx);
return FAILURE;
}
ht_chan = Z_ARRVAL_P(z_arr);
sctx->kw = kw;
sctx->argc = zend_hash_num_elements(ht_chan);
if(sctx->argc==0) {
efree(sctx);
return FAILURE;
}
// Start command construction
redis_cmd_init_sstr(&cmdstr, sctx->argc, kw, strlen(kw));
// Iterate over channels
ZEND_HASH_FOREACH_VAL(ht_chan, z_chan) {
// We want to deal with strings here
zend_string *zstr = zval_get_string(z_chan);
// Grab channel name, prefix if required
key = ZSTR_VAL(zstr);
key_len = ZSTR_LEN(zstr);
key_free = redis_key_prefix(redis_sock, &key, &key_len);
// Add this channel
redis_cmd_append_sstr(&cmdstr, key, key_len);
zend_string_release(zstr);
// Free our key if it was prefixed
if(key_free) efree(key);
} ZEND_HASH_FOREACH_END();
// Push values out
*cmd_len = cmdstr.len;
*cmd = cmdstr.c;
*ctx = (void*)sctx;
// Pick a slot at random
CMD_RAND_SLOT(slot);
return SUCCESS;
}
/* UNSUBSCRIBE/PUNSUBSCRIBE */
int redis_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
zval *z_arr, *z_chan;
HashTable *ht_arr;
smart_string cmdstr = {0};
subscribeContext *sctx = emalloc(sizeof(subscribeContext));
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &z_arr)==FAILURE) {
efree(sctx);
return FAILURE;
}
ht_arr = Z_ARRVAL_P(z_arr);
sctx->argc = zend_hash_num_elements(ht_arr);
if(sctx->argc == 0) {
efree(sctx);
return FAILURE;
}
redis_cmd_init_sstr(&cmdstr, sctx->argc, kw, strlen(kw));
ZEND_HASH_FOREACH_VAL(ht_arr, z_chan) {
char *key = Z_STRVAL_P(z_chan);
strlen_t key_len = Z_STRLEN_P(z_chan);
int key_free;
key_free = redis_key_prefix(redis_sock, &key, &key_len);
redis_cmd_append_sstr(&cmdstr, key, key_len);
if(key_free) efree(key);
} ZEND_HASH_FOREACH_END();
// Push out vals
*cmd_len = cmdstr.len;
*cmd = cmdstr.c;
*ctx = (void*)sctx;
return SUCCESS;
}
/* ZRANGEBYLEX/ZREVRANGEBYLEX */
int redis_zrangebylex_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key, *min, *max;
strlen_t key_len, min_len, max_len;
int argc = ZEND_NUM_ARGS();
zend_long offset, count;
/* We need either 3 or 5 arguments for this to be valid */
if (argc != 3 && argc != 5) {
php_error_docref(0 TSRMLS_CC, E_WARNING, "Must pass either 3 or 5 arguments");
return FAILURE;
}
if(zend_parse_parameters(argc TSRMLS_CC, "sss|ll", &key, &key_len, &min, &min_len,
&max, &max_len, &offset, &count)==FAILURE)
{
return FAILURE;
}
/* min and max must start with '(' or '[', or be either '-' or '+' */
if (min_len < 1 || max_len < 1 ||
(min[0] != '(' && min[0] != '[' &&
(min[0] != '-' || min_len > 1) && (min[0] != '+' || min_len > 1)) ||
(max[0] != '(' && max[0] != '[' &&
(max[0] != '-' || max_len > 1) && (max[0] != '+' || max_len > 1)))
{
php_error_docref(0 TSRMLS_CC, E_WARNING,
"min and max arguments must start with '[' or '('");
return FAILURE;
}
/* Construct command */
if (argc == 3) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", key, key_len, min, min_len,
max, max_len);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksssll", key, key_len, min, min_len,
max, max_len, "LIMIT", 5, offset, count);
}
return SUCCESS;
}
/* ZLEXCOUNT/ZREMRANGEBYLEX */
int redis_gen_zlex_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
char *key, *min, *max;
strlen_t key_len, min_len, max_len;
/* Parse args */
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss", &key, &key_len,
&min, &min_len, &max, &max_len)==FAILURE)
{
return FAILURE;
}
/* Quick sanity check on min/max */
if(min_len<1 || max_len<1 || (min[0]!='(' && min[0]!='[') ||
(max[0]!='(' && max[0]!='['))
{
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"Min and Max arguments must begin with '(' or '['");
return FAILURE;
}
/* Construct command */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "kss", key, key_len, min, min_len,
max, max_len);
return SUCCESS;
}
/* EVAL and EVALSHA */
int redis_eval_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock, char *kw,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *lua;
int argc = 0;
zval *z_arr = NULL, *z_ele;
HashTable *ht_arr;
zend_long num_keys = 0;
smart_string cmdstr = {0};
strlen_t lua_len;
zend_string *zstr;
short prevslot = -1;
/* Parse args */
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|al", &lua, &lua_len,
&z_arr, &num_keys)==FAILURE)
{
return FAILURE;
}
/* Grab arg count */
if (z_arr != NULL) {
ht_arr = Z_ARRVAL_P(z_arr);
argc = zend_hash_num_elements(ht_arr);
}
/* EVAL[SHA] {script || sha1} {num keys} */
redis_cmd_init_sstr(&cmdstr, 2 + argc, kw, strlen(kw));
redis_cmd_append_sstr(&cmdstr, lua, lua_len);
redis_cmd_append_sstr_long(&cmdstr, num_keys);
// Iterate over our args if we have any
if (argc > 0) {
ZEND_HASH_FOREACH_VAL(ht_arr, z_ele) {
zstr = zval_get_string(z_ele);
/* If we're still on a key, prefix it check slot */
if (num_keys-- > 0) {
redis_cmd_append_sstr_key(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr), redis_sock, slot);
/* If we have been passed a slot, all keys must match */
if (slot) {
if (prevslot != -1 && prevslot != *slot) {
zend_string_release(zstr);
php_error_docref(0 TSRMLS_CC, E_WARNING, "All keys do not map to the same slot");
return FAILURE;
}
prevslot = *slot;
}
} else {
redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
}
zend_string_release(zstr);
} ZEND_HASH_FOREACH_END();
} else {
/* Any slot will do */
CMD_RAND_SLOT(slot);
}
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* Commands that take a key followed by a variable list of serializable
* values (RPUSH, LPUSH, SADD, SREM, etc...) */
int redis_key_varval_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
zval *z_args;
smart_string cmdstr = {0};
strlen_t i;
int argc = ZEND_NUM_ARGS();
// We at least need a key and one value
if(argc < 2) {
return FAILURE;
}
// Make sure we at least have a key, and we can get other args
z_args = emalloc(argc * sizeof(zval));
if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
efree(z_args);
return FAILURE;
}
/* Initialize our command */
redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
/* Append key */
zend_string *zstr = zval_get_string(&z_args[0]);
redis_cmd_append_sstr_key(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr), redis_sock, slot);
zend_string_release(zstr);
/* Add members */
for (i = 1; i < argc; i++ ){
redis_cmd_append_sstr_zval(&cmdstr, &z_args[i], redis_sock TSRMLS_CC);
}
// Push out values
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
// Cleanup arg array
efree(z_args);
// Success!
return SUCCESS;
}
/* Commands that take a key and then an array of values */
int redis_key_arr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
zval *z_arr, *z_val;
HashTable *ht_arr;
smart_string cmdstr = {0};
int key_free, val_free, argc = 1;
strlen_t val_len, key_len;
char *key, *val;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", &key, &key_len,
&z_arr) == FAILURE ||
zend_hash_num_elements(Z_ARRVAL_P(z_arr)) == 0)
{
return FAILURE;
}
/* Start constructing our command */
ht_arr = Z_ARRVAL_P(z_arr);
argc += zend_hash_num_elements(ht_arr);
redis_cmd_init_sstr(&cmdstr, argc, kw, strlen(kw));
/* Prefix if required and append the key name */
key_free = redis_key_prefix(redis_sock, &key, &key_len);
redis_cmd_append_sstr(&cmdstr, key, key_len);
CMD_SET_SLOT(slot, key, key_len);
if (key_free) efree(key);
/* Iterate our hash table, serializing and appending values */
ZEND_HASH_FOREACH_VAL(ht_arr, z_val) {
val_free = redis_serialize(redis_sock, z_val, &val, &val_len TSRMLS_CC);
redis_cmd_append_sstr(&cmdstr, val, val_len);
if (val_free) efree(val);
} ZEND_HASH_FOREACH_END();
*cmd_len = cmdstr.len;
*cmd = cmdstr.c;
return SUCCESS;
}
/* Generic function that takes a variable number of keys, with an optional
* timeout value. This can handle various SUNION/SUNIONSTORE/BRPOP type
* commands. */
static int gen_varkey_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, int kw_len, int min_argc, int has_timeout,
char **cmd, int *cmd_len, short *slot)
{
zval *z_args, *z_ele;
HashTable *ht_arr;
char *key;
int key_free, i, tail;
strlen_t key_len;
int single_array = 0, argc = ZEND_NUM_ARGS();
smart_string cmdstr = {0};
long timeout = 0;
short kslot = -1;
zend_string *zstr;
if(argc < min_argc) {
zend_wrong_param_count(TSRMLS_C);
return FAILURE;
}
// Allocate args
z_args = emalloc(argc * sizeof(zval));
if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
efree(z_args);
return FAILURE;
}
// Handle our "single array" case
if(has_timeout == 0) {
single_array = argc==1 && Z_TYPE(z_args[0])==IS_ARRAY;
} else {
single_array = argc==2 && Z_TYPE(z_args[0])==IS_ARRAY &&
Z_TYPE(z_args[1])==IS_LONG;
timeout = Z_LVAL(z_args[1]);
}
// If we're running a single array, rework args
if(single_array) {
ht_arr = Z_ARRVAL(z_args[0]);
argc = zend_hash_num_elements(ht_arr);
if(has_timeout) argc++;
efree(z_args);
z_args = NULL;
/* If the array is empty, we can simply abort */
if (argc == 0) return FAILURE;
}
// Begin construction of our command
redis_cmd_init_sstr(&cmdstr, argc, kw, kw_len);
if(single_array) {
ZEND_HASH_FOREACH_VAL(ht_arr, z_ele) {
zstr = zval_get_string(z_ele);
key = ZSTR_VAL(zstr);
key_len = ZSTR_LEN(zstr);
key_free = redis_key_prefix(redis_sock, &key, &key_len);
// Protect against CROSSLOT errors
if(slot) {
if(kslot == -1) {
kslot = cluster_hash_key(key, key_len);
} else if(cluster_hash_key(key,key_len)!=kslot) {
zend_string_release(zstr);
if(key_free) efree(key);
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"Not all keys hash to the same slot!");
return FAILURE;
}
}
// Append this key, free it if we prefixed
redis_cmd_append_sstr(&cmdstr, key, key_len);
zend_string_release(zstr);
if(key_free) efree(key);
} ZEND_HASH_FOREACH_END();
if(has_timeout) {
redis_cmd_append_sstr_long(&cmdstr, timeout);
}
} else {
if(has_timeout && Z_TYPE(z_args[argc-1])!=IS_LONG) {
php_error_docref(NULL TSRMLS_CC, E_ERROR,
"Timeout value must be a LONG");
efree(z_args);
return FAILURE;
}
tail = has_timeout ? argc-1 : argc;
for(i=0;i= 2.6.12 set options */
if(z_opts && Z_TYPE_P(z_opts) != IS_LONG && Z_TYPE_P(z_opts) != IS_ARRAY
&& Z_TYPE_P(z_opts) != IS_NULL)
{
return FAILURE;
}
// Check for an options array
if(z_opts && Z_TYPE_P(z_opts) == IS_ARRAY) {
HashTable *kt = Z_ARRVAL_P(z_opts);
zend_string *zkey;
ulong idx;
zval *v;
PHPREDIS_NOTUSED(idx);
/* Iterate our option array */
ZEND_HASH_FOREACH_KEY_VAL(kt, idx, zkey, v) {
ZVAL_DEREF(v);
/* Detect PX or EX argument and validate timeout */
if (zkey && IS_EX_PX_ARG(ZSTR_VAL(zkey))) {
/* Set expire type */
exp_type = ZSTR_VAL(zkey);
/* Try to extract timeout */
if (Z_TYPE_P(v) == IS_LONG) {
expire = Z_LVAL_P(v);
} else if (Z_TYPE_P(v) == IS_STRING) {
expire = atol(Z_STRVAL_P(v));
}
/* Expiry can't be set < 1 */
if (expire < 1) {
return FAILURE;
}
} else if (Z_TYPE_P(v) == IS_STRING && IS_NX_XX_ARG(Z_STRVAL_P(v))) {
set_type = Z_STRVAL_P(v);
}
} ZEND_HASH_FOREACH_END();
} else if(z_opts && Z_TYPE_P(z_opts) == IS_LONG) {
/* Grab expiry and fail if it's < 1 */
expire = Z_LVAL_P(z_opts);
if (expire < 1) {
return FAILURE;
}
}
/* Now let's construct the command we want */
if(exp_type && set_type) {
/* SET NX|XX PX|EX */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "SET", "kvsls", key, key_len, z_value,
exp_type, 2, expire, set_type, 2);
} else if(exp_type) {
/* SET PX|EX */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "SET", "kvsl", key, key_len, z_value,
exp_type, 2, expire);
} else if(set_type) {
/* SET NX|XX */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "SET", "kvs", key, key_len, z_value,
set_type, 2);
} else if(expire > 0) {
/* Backward compatible SETEX redirection */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "SETEX", "klv", key, key_len, expire,
z_value);
} else {
/* SET */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "SET", "kv", key, key_len, z_value);
}
return SUCCESS;
}
/* BRPOPLPUSH */
int redis_brpoplpush_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key1, *key2;
strlen_t key1_len, key2_len;
int key1_free, key2_free;
short slot1, slot2;
zend_long timeout;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssl", &key1, &key1_len,
&key2, &key2_len, &timeout)==FAILURE)
{
return FAILURE;
}
// Key prefixing
key1_free = redis_key_prefix(redis_sock, &key1, &key1_len);
key2_free = redis_key_prefix(redis_sock, &key2, &key2_len);
// In cluster mode, verify the slots match
if (slot) {
slot1 = cluster_hash_key(key1, key1_len);
slot2 = cluster_hash_key(key2, key2_len);
if(slot1 != slot2) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"Keys hash to different slots!");
if(key1_free) efree(key1);
if(key2_free) efree(key2);
return FAILURE;
}
// Both slots are the same
*slot = slot1;
}
// Consistency with Redis, if timeout < 0 use RPOPLPUSH
if(timeout < 0) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "RPOPLPUSH", "ss", key1, key1_len,
key2, key2_len);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "BRPOPLPUSH", "ssd", key1, key1_len,
key2, key2_len, timeout);
}
if (key1_free) efree(key1);
if (key2_free) efree(key2);
return SUCCESS;
}
/* To maintain backward compatibility with earlier versions of phpredis, we
* allow for an optional "increment by" argument for INCR and DECR even though
* that's not how Redis proper works */
#define TYPE_INCR 0
#define TYPE_DECR 1
/* Handle INCR(BY) and DECR(BY) depending on optional increment value */
static int
redis_atomic_increment(INTERNAL_FUNCTION_PARAMETERS, int type,
RedisSock *redis_sock, char **cmd, int *cmd_len,
short *slot, void **ctx)
{
char *key;
strlen_t key_len;
zend_long val = 1;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &key, &key_len,
&val)==FAILURE)
{
return FAILURE;
}
/* If our value is 1 we use INCR/DECR. For other values, treat the call as
* an INCRBY or DECRBY call */
if (type == TYPE_INCR) {
if (val == 1) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "INCR", "k", key, key_len);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "INCRBY", "kl", key, key_len, val);
}
} else {
if (val == 1) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "DECR", "k", key, key_len);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "DECRBY", "kl", key, key_len, val);
}
}
/* Success */
return SUCCESS;
}
/* INCR */
int redis_incr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU,
TYPE_INCR, redis_sock, cmd, cmd_len, slot, ctx);
}
/* DECR */
int redis_decr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return redis_atomic_increment(INTERNAL_FUNCTION_PARAM_PASSTHRU,
TYPE_DECR, redis_sock, cmd, cmd_len, slot, ctx);
}
/* HINCRBY */
int redis_hincrby_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key, *mem;
strlen_t key_len, mem_len;
zend_long byval;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssl", &key, &key_len,
&mem, &mem_len, &byval)==FAILURE)
{
return FAILURE;
}
// Construct command
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "HINCRBY", "ksl", key, key_len, mem, mem_len, byval);
// Success
return SUCCESS;
}
/* HINCRBYFLOAT */
int redis_hincrbyfloat_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key, *mem;
strlen_t key_len, mem_len;
double byval;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssd", &key, &key_len,
&mem, &mem_len, &byval)==FAILURE)
{
return FAILURE;
}
// Construct command
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "HINCRBYFLOAT", "ksf", key, key_len, mem,
mem_len, byval);
// Success
return SUCCESS;
}
/* HMGET */
int redis_hmget_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key;
zval *z_arr, *z_mems, *z_mem;
int i, count, valid=0, key_free;
strlen_t key_len;
HashTable *ht_arr;
smart_string cmdstr = {0};
// Parse arguments
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", &key, &key_len,
&z_arr)==FAILURE)
{
return FAILURE;
}
// Our HashTable
ht_arr = Z_ARRVAL_P(z_arr);
// We can abort if we have no elements
if((count = zend_hash_num_elements(ht_arr))==0) {
return FAILURE;
}
// Allocate memory for mems+1 so we can have a sentinel
z_mems = ecalloc(count + 1, sizeof(zval));
// Iterate over our member array
ZEND_HASH_FOREACH_VAL(ht_arr, z_mem) {
ZVAL_DEREF(z_mem);
// We can only handle string or long values here
if ((Z_TYPE_P(z_mem) == IS_STRING && Z_STRLEN_P(z_mem) > 0)
|| Z_TYPE_P(z_mem) == IS_LONG
) {
// Copy into our member array
ZVAL_ZVAL(&z_mems[valid], z_mem, 1, 0);
convert_to_string(&z_mems[valid]);
// Increment the member count to actually send
valid++;
}
} ZEND_HASH_FOREACH_END();
// If nothing was valid, fail
if(valid == 0) {
efree(z_mems);
return FAILURE;
}
// Sentinel so we can free this even if it's used and then we discard
// the transaction manually or there is a transaction failure
ZVAL_NULL(&z_mems[valid]);
// Start command construction
redis_cmd_init_sstr(&cmdstr, valid+1, "HMGET", sizeof("HMGET")-1);
// Prefix our key
key_free = redis_key_prefix(redis_sock, &key, &key_len);
redis_cmd_append_sstr(&cmdstr, key, key_len);
// Iterate over members, appending as arguments
for(i=0;i value array
ZEND_HASH_FOREACH_KEY_VAL(ht_vals, idx, zkey, z_val) {
char *mem, *val, kbuf[40];
strlen_t val_len;
int val_free;
unsigned int mem_len;
// If the hash key is an integer, convert it to a string
if (zkey) {
mem_len = ZSTR_LEN(zkey);
mem = ZSTR_VAL(zkey);
} else {
mem_len = snprintf(kbuf, sizeof(kbuf), "%ld", (long)idx);
mem = (char*)kbuf;
}
// Serialize value (if directed)
val_free = redis_serialize(redis_sock, z_val, &val, &val_len TSRMLS_CC);
// Append the key and value to our command
redis_cmd_append_sstr(&cmdstr, mem, mem_len);
redis_cmd_append_sstr(&cmdstr, val, val_len);
// Free our value if we serialized it
if (val_free) efree(val);
} ZEND_HASH_FOREACH_END();
// Set slot if directed
CMD_SET_SLOT(slot,key,key_len);
// Free our key if we prefixed it
if(key_free) efree(key);
// Push return pointers
*cmd_len = cmdstr.len;
*cmd = cmdstr.c;
// Success!
return SUCCESS;
}
/* HSTRLEN */
int
redis_hstrlen_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key, *field;
strlen_t key_len, field_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &key, &key_len,
&field, &field_len) == FAILURE
) {
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "HSTRLEN", "ks", key, key_len, field, field_len);
return SUCCESS;
}
/* BITPOS */
int redis_bitpos_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key;
int argc;
zend_long bit, start, end;
strlen_t key_len;
argc = ZEND_NUM_ARGS();
if(zend_parse_parameters(argc TSRMLS_CC, "sl|ll", &key, &key_len, &bit,
&start, &end)==FAILURE)
{
return FAILURE;
}
// Prevalidate bit
if(bit != 0 && bit != 1) {
return FAILURE;
}
// Construct command based on arg count
if(argc == 2) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "BITPOS", "kd", key, key_len, bit);
} else if(argc == 3) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "BITPOS", "kdd", key, key_len, bit,
start);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "BITPOS", "kddd", key, key_len, bit,
start, end);
}
return SUCCESS;
}
/* BITOP */
int redis_bitop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
zval *z_args;
char *key;
strlen_t key_len;
int i, key_free, argc = ZEND_NUM_ARGS();
smart_string cmdstr = {0};
short kslot;
zend_string *zstr;
// Allocate space for args, parse them as an array
z_args = emalloc(argc * sizeof(zval));
if (zend_get_parameters_array(ht, argc, z_args) == FAILURE ||
argc < 3 || Z_TYPE(z_args[0]) != IS_STRING)
{
efree(z_args);
return FAILURE;
}
// If we were passed a slot pointer, init to a sentinel value
if(slot) *slot = -1;
// Initialize command construction, add our operation argument
redis_cmd_init_sstr(&cmdstr, argc, "BITOP", sizeof("BITOP")-1);
redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[0]), Z_STRLEN(z_args[0]));
// Now iterate over our keys argument
for(i=1;iauth) zend_string_release(redis_sock->auth);
redis_sock->auth = zend_string_init(pw, pw_len, 0);
// Success
return SUCCESS;
}
/* SETBIT */
int redis_setbit_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key;
strlen_t key_len;
zend_long offset;
zend_bool val;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "slb", &key, &key_len,
&offset, &val)==FAILURE)
{
return FAILURE;
}
// Validate our offset
if(offset < BITOP_MIN_OFFSET || offset > BITOP_MAX_OFFSET) {
php_error_docref(0 TSRMLS_CC, E_WARNING,
"Invalid OFFSET for bitop command (must be between 0-2^32-1)");
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "SETBIT", "kld", key, key_len, offset, (int)val);
return SUCCESS;
}
/* LINSERT */
int redis_linsert_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key, *pos;
strlen_t key_len, pos_len;
zval *z_val, *z_pivot;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sszz", &key, &key_len,
&pos, &pos_len, &z_pivot, &z_val)==FAILURE)
{
return FAILURE;
}
// Validate position
if(strncasecmp(pos, "after", 5) && strncasecmp(pos, "before", 6)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"Position must be either 'BEFORE' or 'AFTER'");
return FAILURE;
}
/* Construct command */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "LINSERT", "ksvv", key, key_len, pos,
pos_len, z_pivot, z_val);
// Success
return SUCCESS;
}
/* LREM */
int redis_lrem_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key;
strlen_t key_len;
zend_long count = 0;
zval *z_val;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|l", &key, &key_len,
&z_val, &count)==FAILURE)
{
return FAILURE;
}
/* Construct command */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "LREM", "kdv", key, key_len, count, z_val);
// Success!
return SUCCESS;
}
int redis_smove_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *src, *dst;
strlen_t src_len, dst_len;
int src_free, dst_free;
zval *z_val;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssz", &src, &src_len,
&dst, &dst_len, &z_val)==FAILURE)
{
return FAILURE;
}
src_free = redis_key_prefix(redis_sock, &src, &src_len);
dst_free = redis_key_prefix(redis_sock, &dst, &dst_len);
// Protect against a CROSSSLOT error
if (slot) {
short slot1 = cluster_hash_key(src, src_len);
short slot2 = cluster_hash_key(dst, dst_len);
if(slot1 != slot2) {
php_error_docref(0 TSRMLS_CC, E_WARNING,
"Source and destination keys don't hash to the same slot!");
if(src_free) efree(src);
if(dst_free) efree(dst);
return FAILURE;
}
*slot = slot1;
}
// Construct command
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "SMOVE", "ssv", src, src_len, dst,
dst_len, z_val);
// Cleanup
if(src_free) efree(src);
if(dst_free) efree(dst);
// Succcess!
return SUCCESS;
}
/* Generic command construction for HSET and HSETNX */
static int gen_hset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot)
{
char *key, *mem;
strlen_t key_len, mem_len;
zval *z_val;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssz", &key, &key_len,
&mem, &mem_len, &z_val)==FAILURE)
{
return FAILURE;
}
/* Construct command */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, kw, "ksv", key, key_len, mem, mem_len, z_val);
// Success
return SUCCESS;
}
/* HSET */
int redis_hset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_hset_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "HSET",
cmd, cmd_len, slot);
}
/* HSETNX */
int redis_hsetnx_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_hset_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "HSETNX",
cmd, cmd_len, slot);
}
/* SRANDMEMBER */
int redis_srandmember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx,
short *have_count)
{
char *key;
strlen_t key_len;
zend_long count;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &key, &key_len,
&count)==FAILURE)
{
return FAILURE;
}
// Set our have count flag
*have_count = ZEND_NUM_ARGS() == 2;
// Two args means we have the optional COUNT
if (*have_count) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "SRANDMEMBER", "kl", key, key_len, count);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "SRANDMEMBER", "k", key, key_len);
}
return SUCCESS;
}
/* ZINCRBY */
int redis_zincrby_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key;
strlen_t key_len;
double incrby;
zval *z_val;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sdz", &key, &key_len,
&incrby, &z_val)==FAILURE)
{
return FAILURE;
}
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "ZINCRBY", "kfv", key, key_len, incrby, z_val);
return SUCCESS;
}
/* SORT */
int redis_sort_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
int *using_store, char **cmd, int *cmd_len, short *slot,
void **ctx)
{
zval *z_opts=NULL, *z_ele, z_argv;
char *key;
HashTable *ht_opts;
smart_string cmdstr = {0};
strlen_t key_len;
int key_free;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|a", &key, &key_len,
&z_opts)==FAILURE)
{
return FAILURE;
}
// Default that we're not using store
*using_store = 0;
// If we don't have an options array, the command is quite simple
if (!z_opts || zend_hash_num_elements(Z_ARRVAL_P(z_opts)) == 0) {
// Construct command
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "SORT", "k", key, key_len);
/* Not storing */
*using_store = 0;
return SUCCESS;
}
// Create our hash table to hold our sort arguments
array_init(&z_argv);
// SORT
key_free = redis_key_prefix(redis_sock, &key, &key_len);
add_next_index_stringl(&z_argv, key, key_len);
if (key_free) efree(key);
// Set slot
CMD_SET_SLOT(slot,key,key_len);
// Grab the hash table
ht_opts = Z_ARRVAL_P(z_opts);
// Handle BY pattern
if (((z_ele = zend_hash_str_find(ht_opts, "by", sizeof("by") - 1)) != NULL ||
(z_ele = zend_hash_str_find(ht_opts, "BY", sizeof("BY") - 1)) != NULL
) && Z_TYPE_P(z_ele) == IS_STRING
) {
// "BY" option is disabled in cluster
if(slot) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"SORT BY option is not allowed in Redis Cluster");
zval_dtor(&z_argv);
return FAILURE;
}
// ... BY
add_next_index_stringl(&z_argv, "BY", sizeof("BY") - 1);
add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
}
// Handle ASC/DESC option
if (((z_ele = zend_hash_str_find(ht_opts, "sort", sizeof("sort") - 1)) != NULL ||
(z_ele = zend_hash_str_find(ht_opts, "SORT", sizeof("SORT") - 1)) != NULL
) && Z_TYPE_P(z_ele) == IS_STRING
) {
// 'asc'|'desc'
add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
}
// STORE option
if (((z_ele = zend_hash_str_find(ht_opts, "store", sizeof("store") - 1)) != NULL ||
(z_ele = zend_hash_str_find(ht_opts, "STORE", sizeof("STORE") - 1)) != NULL
) && Z_TYPE_P(z_ele) == IS_STRING
) {
// Slot verification
int cross_slot = slot && *slot != cluster_hash_key(
Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
if(cross_slot) {
php_error_docref(0 TSRMLS_CC, E_WARNING,
"Error, SORT key and STORE key have different slots!");
zval_dtor(&z_argv);
return FAILURE;
}
// STORE
add_next_index_stringl(&z_argv, "STORE", sizeof("STORE") - 1);
add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
// We are using STORE
*using_store = 1;
}
// GET option
if (((z_ele = zend_hash_str_find(ht_opts, "get", sizeof("get") - 1)) != NULL ||
(z_ele = zend_hash_str_find(ht_opts, "GET", sizeof("GET") - 1)) != NULL
) && (Z_TYPE_P(z_ele) == IS_STRING || Z_TYPE_P(z_ele) == IS_ARRAY)
) {
// Disabled in cluster
if(slot) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"GET option for SORT disabled in Redis Cluster");
zval_dtor(&z_argv);
return FAILURE;
}
// If it's a string just add it
if (Z_TYPE_P(z_ele) == IS_STRING) {
add_next_index_stringl(&z_argv, "GET", sizeof("GET") - 1);
add_next_index_stringl(&z_argv, Z_STRVAL_P(z_ele), Z_STRLEN_P(z_ele));
} else {
int added=0;
zval *z_key;
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_ele), z_key) {
// If we can't get the data, or it's not a string, skip
if (z_key == NULL || Z_TYPE_P(z_key) != IS_STRING) {
continue;
}
/* Add get per thing we're getting */
add_next_index_stringl(&z_argv, "GET", sizeof("GET") - 1);
// Add this key to our argv array
add_next_index_stringl(&z_argv, Z_STRVAL_P(z_key), Z_STRLEN_P(z_key));
added++;
} ZEND_HASH_FOREACH_END();
// Make sure we were able to add at least one
if(added==0) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"Array of GET values requested, but none are valid");
zval_dtor(&z_argv);
return FAILURE;
}
}
}
// ALPHA
if (((z_ele = zend_hash_str_find(ht_opts, "alpha", sizeof("alpha") - 1)) != NULL ||
(z_ele = zend_hash_str_find(ht_opts, "ALPHA", sizeof("ALPHA") - 1)) != NULL) &&
zval_is_true(z_ele)
) {
add_next_index_stringl(&z_argv, "ALPHA", sizeof("ALPHA") - 1);
}
// LIMIT
if (((z_ele = zend_hash_str_find(ht_opts, "limit", sizeof("limit") - 1)) != NULL ||
(z_ele = zend_hash_str_find(ht_opts, "LIMIT", sizeof("LIMIT") - 1)) != NULL
) && Z_TYPE_P(z_ele) == IS_ARRAY
) {
HashTable *ht_off = Z_ARRVAL_P(z_ele);
zval *z_off, *z_cnt;
if ((z_off = zend_hash_index_find(ht_off, 0)) != NULL &&
(z_cnt = zend_hash_index_find(ht_off, 1)) != NULL
) {
if ((Z_TYPE_P(z_off) != IS_STRING && Z_TYPE_P(z_off) != IS_LONG) ||
(Z_TYPE_P(z_cnt) != IS_STRING && Z_TYPE_P(z_cnt) != IS_LONG)
) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"LIMIT options on SORT command must be longs or strings");
zval_dtor(&z_argv);
return FAILURE;
}
// Add LIMIT argument
add_next_index_stringl(&z_argv, "LIMIT", sizeof("LIMIT") - 1);
long low, high;
if (Z_TYPE_P(z_off) == IS_STRING) {
low = atol(Z_STRVAL_P(z_off));
} else {
low = Z_LVAL_P(z_off);
}
if (Z_TYPE_P(z_cnt) == IS_STRING) {
high = atol(Z_STRVAL_P(z_cnt));
} else {
high = Z_LVAL_P(z_cnt);
}
// Add our two LIMIT arguments
add_next_index_long(&z_argv, low);
add_next_index_long(&z_argv, high);
}
}
// Start constructing our command
HashTable *ht_argv = Z_ARRVAL_P(&z_argv);
redis_cmd_init_sstr(&cmdstr, zend_hash_num_elements(ht_argv), "SORT",
sizeof("SORT")-1);
// Iterate through our arguments
ZEND_HASH_FOREACH_VAL(ht_argv, z_ele) {
// Args are strings or longs
if (Z_TYPE_P(z_ele) == IS_STRING) {
redis_cmd_append_sstr(&cmdstr,Z_STRVAL_P(z_ele),
Z_STRLEN_P(z_ele));
} else {
redis_cmd_append_sstr_long(&cmdstr, Z_LVAL_P(z_ele));
}
} ZEND_HASH_FOREACH_END();
/* Clean up our arguments array. Note we don't have to free any prefixed
* key as that we didn't duplicate the pointer if we prefixed */
zval_dtor(&z_argv);
// Push our length and command
*cmd_len = cmdstr.len;
*cmd = cmdstr.c;
// Success!
return SUCCESS;
}
/* HDEL */
int redis_hdel_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
zval *z_args;
smart_string cmdstr = {0};
char *arg;
int arg_free, i;
strlen_t arg_len;
int argc = ZEND_NUM_ARGS();
zend_string *zstr;
// We need at least KEY and one member
if(argc < 2) {
return FAILURE;
}
// Grab arguments as an array
z_args = emalloc(argc * sizeof(zval));
if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
efree(z_args);
return FAILURE;
}
// Get first argument (the key) as a string
zstr = zval_get_string(&z_args[0]);
arg = ZSTR_VAL(zstr);
arg_len = ZSTR_LEN(zstr);
// Prefix
arg_free = redis_key_prefix(redis_sock, &arg, &arg_len);
// Start command construction
redis_cmd_init_sstr(&cmdstr, argc, "HDEL", sizeof("HDEL")-1);
redis_cmd_append_sstr(&cmdstr, arg, arg_len);
// Set our slot, free key if we prefixed it
CMD_SET_SLOT(slot,arg,arg_len);
zend_string_release(zstr);
if(arg_free) efree(arg);
// Iterate through the members we're removing
for(i=1;i 4) {
// Only one score-element pair can be specified in this mode.
efree(z_args);
return FAILURE;
}
incr = 1;
}
}
} ZEND_HASH_FOREACH_END();
argc = num - 1;
if (exp_type) argc++;
argc += ch + incr;
i++;
} else {
argc = num;
}
// Prefix our key
zstr = zval_get_string(&z_args[0]);
key = ZSTR_VAL(zstr);
key_len = ZSTR_LEN(zstr);
key_free = redis_key_prefix(redis_sock, &key, &key_len);
// Start command construction
redis_cmd_init_sstr(&cmdstr, argc, "ZADD", sizeof("ZADD")-1);
redis_cmd_append_sstr(&cmdstr, key, key_len);
// Set our slot, free key if we prefixed it
CMD_SET_SLOT(slot,key,key_len);
zend_string_release(zstr);
if(key_free) efree(key);
if (exp_type) redis_cmd_append_sstr(&cmdstr, exp_type, 2);
if (ch) redis_cmd_append_sstr(&cmdstr, "CH", 2);
if (incr) redis_cmd_append_sstr(&cmdstr, "INCR", 4);
// Now the rest of our arguments
while (i < num) {
// Append score and member
if (Z_TYPE(z_args[i]) == IS_STRING && (
/* The score values should be the string representation of a double
* precision floating point number. +inf and -inf values are valid
* values as well. */
strncasecmp(Z_STRVAL(z_args[i]), "-inf", 4) == 0 ||
strncasecmp(Z_STRVAL(z_args[i]), "+inf", 4) == 0
)) {
redis_cmd_append_sstr(&cmdstr, Z_STRVAL(z_args[i]), Z_STRLEN(z_args[i]));
} else {
redis_cmd_append_sstr_dbl(&cmdstr, zval_get_double(&z_args[i]));
}
// serialize value if requested
val_free = redis_serialize(redis_sock, &z_args[i+1], &val, &val_len
TSRMLS_CC);
redis_cmd_append_sstr(&cmdstr, val, val_len);
// Free value if we serialized
if(val_free) efree(val);
i += 2;
}
// Push output values
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
// Cleanup args
efree(z_args);
return SUCCESS;
}
/* OBJECT */
int redis_object_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
REDIS_REPLY_TYPE *rtype, char **cmd, int *cmd_len,
short *slot, void **ctx)
{
char *key, *subcmd;
strlen_t key_len, subcmd_len;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &subcmd,
&subcmd_len, &key, &key_len)==FAILURE)
{
return FAILURE;
}
// Format our command
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "OBJECT", "sk", subcmd, subcmd_len, key, key_len);
// Push the reply type to our caller
if(subcmd_len == 8 && (!strncasecmp(subcmd,"refcount",8) ||
!strncasecmp(subcmd,"idletime",8)))
{
*rtype = TYPE_INT;
} else if(subcmd_len == 8 && !strncasecmp(subcmd, "encoding", 8)) {
*rtype = TYPE_BULK;
} else {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"Invalid subcommand sent to OBJECT");
efree(*cmd);
return FAILURE;
}
// Success
return SUCCESS;
}
/* GEODIST */
int redis_geodist_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key, *source, *dest, *unit = NULL;
strlen_t keylen, sourcelen, destlen, unitlen;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss|s", &key, &keylen,
&source, &sourcelen, &dest, &destlen, &unit,
&unitlen) == FAILURE)
{
return FAILURE;
}
/* Construct command */
if (unit != NULL) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "GEODIST", "ksss", key, keylen, source,
sourcelen, dest, destlen, unit, unitlen);
} else {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "GEODIST", "kss", key, keylen, source,
sourcelen, dest, destlen);
}
return SUCCESS;
}
/* Helper function to extract optional arguments for GEORADIUS and GEORADIUSBYMEMBER */
static void get_georadius_opts(HashTable *ht, int *withcoord, int *withdist,
int *withhash, long *count, geoSortType *sort)
{
ulong idx;
char *optstr;
zend_string *zkey;
zval *optval;
PHPREDIS_NOTUSED(idx);
/* Iterate over our argument array, collating which ones we have */
ZEND_HASH_FOREACH_KEY_VAL(ht, idx, zkey, optval) {
ZVAL_DEREF(optval);
/* If the key is numeric it's a non value option */
if (zkey) {
if (ZSTR_LEN(zkey) == 5 && !strcasecmp(ZSTR_VAL(zkey), "count") && Z_TYPE_P(optval) == IS_LONG) {
*count = Z_LVAL_P(optval);
}
} else {
/* Option needs to be a string */
if (Z_TYPE_P(optval) != IS_STRING) continue;
optstr = Z_STRVAL_P(optval);
if (!strcasecmp(optstr, "withcoord")) {
*withcoord = 1;
} else if (!strcasecmp(optstr, "withdist")) {
*withdist = 1;
} else if (!strcasecmp(optstr, "withhash")) {
*withhash = 1;
} else if (!strcasecmp(optstr, "asc")) {
*sort = SORT_ASC;
} else if (!strcasecmp(optstr, "desc")) {
*sort = SORT_DESC;
}
}
} ZEND_HASH_FOREACH_END();
}
/* Helper to append options to a GEORADIUS or GEORADIUSBYMEMBER command */
void append_georadius_opts(smart_string *str, int withcoord, int withdist,
int withhash, long count, geoSortType sort)
{
/* WITHCOORD option */
if (withcoord)
REDIS_CMD_APPEND_SSTR_STATIC(str, "WITHCOORD");
/* WITHDIST option */
if (withdist)
REDIS_CMD_APPEND_SSTR_STATIC(str, "WITHDIST");
/* WITHHASH option */
if (withhash)
REDIS_CMD_APPEND_SSTR_STATIC(str, "WITHHASH");
/* Append sort if it's not GEO_NONE */
if (sort == SORT_ASC) {
REDIS_CMD_APPEND_SSTR_STATIC(str, "ASC");
} else if (sort == SORT_DESC) {
REDIS_CMD_APPEND_SSTR_STATIC(str, "DESC");
}
/* Append our count if we've got one */
if (count > 0) {
REDIS_CMD_APPEND_SSTR_STATIC(str, "COUNT");
redis_cmd_append_sstr_long(str, count);
}
}
/* GEORADIUS */
int redis_georadius_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key, *unit;
strlen_t keylen, unitlen;
int keyfree, withcoord = 0, withdist = 0, withhash = 0;
long count = 0;
geoSortType sort = SORT_NONE;
double lng, lat, radius;
zval *opts = NULL;
smart_string cmdstr = {0};
int argc;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sddds|a", &key, &keylen,
&lng, &lat, &radius, &unit, &unitlen, &opts)
== FAILURE)
{
return FAILURE;
}
/* Parse any GEORADIUS options we have */
if (opts != NULL) {
get_georadius_opts(Z_ARRVAL_P(opts), &withcoord, &withdist, &withhash,
&count, &sort);
}
/* Calculate the number of arguments we're going to send, five required plus
* options. */
argc = 5 + withcoord + withdist + withhash + (sort != SORT_NONE);
if (count != 0) argc += 2;
/* Begin construction of our command */
REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "GEORADIUS");
/* Apply any key prefix */
keyfree = redis_key_prefix(redis_sock, &key, &keylen);
/* Append required arguments */
redis_cmd_append_sstr(&cmdstr, key, keylen);
redis_cmd_append_sstr_dbl(&cmdstr, lng);
redis_cmd_append_sstr_dbl(&cmdstr, lat);
redis_cmd_append_sstr_dbl(&cmdstr, radius);
redis_cmd_append_sstr(&cmdstr, unit, unitlen);
/* Append optional arguments */
append_georadius_opts(&cmdstr, withcoord, withdist, withhash, count, sort);
/* Free key if it was prefixed */
if (keyfree) efree(key);
/* Set slot, command and len, and return */
CMD_SET_SLOT(slot, key, keylen);
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] */
int redis_georadiusbymember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *key, *mem, *unit;
strlen_t keylen, memlen, unitlen;
int keyfree, argc, withcoord = 0, withdist = 0, withhash = 0;
long count = 0;
double radius;
geoSortType sort = SORT_NONE;
zval *opts = NULL;
smart_string cmdstr = {0};
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssds|a", &key, &keylen,
&mem, &memlen, &radius, &unit, &unitlen, &opts) == FAILURE)
{
return FAILURE;
}
if (opts != NULL) {
get_georadius_opts(Z_ARRVAL_P(opts), &withcoord, &withdist, &withhash, &count, &sort);
}
/* Calculate argc */
argc = 4 + withcoord + withdist + withhash + (sort != SORT_NONE);
if (count != 0) argc += 2;
/* Begin command construction*/
REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "GEORADIUSBYMEMBER");
/* Prefix our key if we're prefixing */
keyfree = redis_key_prefix(redis_sock, &key, &keylen);
/* Append required arguments */
redis_cmd_append_sstr(&cmdstr, key, keylen);
redis_cmd_append_sstr(&cmdstr, mem, memlen);
redis_cmd_append_sstr_long(&cmdstr, radius);
redis_cmd_append_sstr(&cmdstr, unit, unitlen);
/* Append options */
append_georadius_opts(&cmdstr, withcoord, withdist, withhash, count, sort);
/* Free key if we prefixed */
if (keyfree) efree(key);
CMD_SET_SLOT(slot, key, keylen);
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* MIGRATE */
int redis_migrate_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
smart_string cmdstr = {0};
char *host, *key;
int argc, keyfree;
zval *z_keys, *z_key;
strlen_t hostlen, keylen;
zend_long destdb, port, timeout;
zend_bool copy = 0, replace = 0;
zend_string *zstr;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "slzll|bb", &host, &hostlen, &port,
&z_keys, &destdb, &timeout, ©, &replace) == FAILURE)
{
return FAILURE;
}
/* Protect against being passed an array with zero elements */
if (Z_TYPE_P(z_keys) == IS_ARRAY && zend_hash_num_elements(Z_ARRVAL_P(z_keys)) == 0) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Keys array cannot be empty");
return FAILURE;
}
/* host, port, key|"", dest-db, timeout, [copy, replace] [KEYS key1..keyN] */
argc = 5 + copy + replace;
if (Z_TYPE_P(z_keys) == IS_ARRAY) {
/* +1 for the "KEYS" argument itself */
argc += 1 + zend_hash_num_elements(Z_ARRVAL_P(z_keys));
}
/* Initialize MIGRATE command with host and port */
REDIS_CMD_INIT_SSTR_STATIC(&cmdstr, argc, "MIGRATE");
redis_cmd_append_sstr(&cmdstr, host, hostlen);
redis_cmd_append_sstr_long(&cmdstr, port);
/* If passed a keys array the keys come later, otherwise pass the key to
* migrate here */
if (Z_TYPE_P(z_keys) == IS_ARRAY) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "");
} else {
/* Grab passed value as a string */
zstr = zval_get_string(z_keys);
/* We may need to prefix our string */
key = ZSTR_VAL(zstr);
keylen = ZSTR_LEN(zstr);
keyfree = redis_key_prefix(redis_sock, &key, &keylen);
/* Add key to migrate */
redis_cmd_append_sstr(&cmdstr, key, keylen);
zend_string_release(zstr);
if (keyfree) efree(key);
}
redis_cmd_append_sstr_long(&cmdstr, destdb);
redis_cmd_append_sstr_long(&cmdstr, timeout);
REDIS_CMD_APPEND_SSTR_OPT_STATIC(&cmdstr, copy, "COPY");
REDIS_CMD_APPEND_SSTR_OPT_STATIC(&cmdstr, replace, "REPLACE");
/* Append actual keys if we've got a keys array */
if (Z_TYPE_P(z_keys) == IS_ARRAY) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "KEYS");
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(z_keys), z_key) {
zstr = zval_get_string(z_key);
key = ZSTR_VAL(zstr);
keylen = ZSTR_LEN(zstr);
keyfree = redis_key_prefix(redis_sock, &key, &keylen);
/* Append the key */
redis_cmd_append_sstr(&cmdstr, key, keylen);
zend_string_release(zstr);
if (keyfree) efree(key);
} ZEND_HASH_FOREACH_END();
}
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
return SUCCESS;
}
/* DEL */
int redis_del_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"DEL", sizeof("DEL")-1, 1, 0, cmd, cmd_len, slot);
}
/* WATCH */
int redis_watch_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"WATCH", sizeof("WATCH")-1, 1, 0, cmd, cmd_len, slot);
}
/* BLPOP */
int redis_blpop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"BLPOP", sizeof("BLPOP")-1, 2, 1, cmd, cmd_len, slot);
}
/* BRPOP */
int redis_brpop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"BRPOP", sizeof("BRPOP")-1, 1, 1, cmd, cmd_len, slot);
}
/* SINTER */
int redis_sinter_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"SINTER", sizeof("SINTER")-1, 1, 0, cmd, cmd_len, slot);
}
/* SINTERSTORE */
int redis_sinterstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"SINTERSTORE", sizeof("SINTERSTORE")-1, 1, 0, cmd, cmd_len, slot);
}
/* SUNION */
int redis_sunion_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"SUNION", sizeof("SUNION")-1, 1, 0, cmd, cmd_len, slot);
}
/* SUNIONSTORE */
int redis_sunionstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"SUNIONSTORE", sizeof("SUNIONSTORE")-1, 2, 0, cmd, cmd_len, slot);
}
/* SDIFF */
int redis_sdiff_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, "SDIFF",
sizeof("SDIFF")-1, 1, 0, cmd, cmd_len, slot);
}
/* SDIFFSTORE */
int redis_sdiffstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
return gen_varkey_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
"SDIFFSTORE", sizeof("SDIFFSTORE")-1, 1, 0, cmd, cmd_len, slot);
}
/* COMMAND */
int redis_command_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx)
{
char *kw=NULL;
zval *z_arg;
strlen_t kw_len;
/* Parse our args */
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sz", &kw, &kw_len,
&z_arg)==FAILURE)
{
return FAILURE;
}
/* Construct our command */
if(!kw) {
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "COMMAND", "");
} else if (!z_arg) {
/* Sanity check */
if (strncasecmp(kw, "count", sizeof("count") - 1)) {
return FAILURE;
}
/* COMMAND COUNT */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "COMMAND", "s", "COUNT", sizeof("COUNT") - 1);
} else if (Z_TYPE_P(z_arg) == IS_STRING) {
/* Sanity check */
if (strncasecmp(kw, "info", sizeof("info") - 1)) {
return FAILURE;
}
/* COMMAND INFO */
*cmd_len = REDIS_CMD_SPPRINTF(cmd, "COMMAND", "ss", "INFO", sizeof("INFO") - 1,
Z_STRVAL_P(z_arg), Z_STRLEN_P(z_arg));
} else {
int arr_len;
/* Sanity check on args */
if(strncasecmp(kw, "getkeys", sizeof("getkeys")-1) ||
Z_TYPE_P(z_arg)!=IS_ARRAY ||
(arr_len=zend_hash_num_elements(Z_ARRVAL_P(z_arg)))<1)
{
return FAILURE;
}
zval *z_ele;
HashTable *ht_arr = Z_ARRVAL_P(z_arg);
smart_string cmdstr = {0};
redis_cmd_init_sstr(&cmdstr, 1 + arr_len, "COMMAND", sizeof("COMMAND")-1);
redis_cmd_append_sstr(&cmdstr, "GETKEYS", sizeof("GETKEYS")-1);
ZEND_HASH_FOREACH_VAL(ht_arr, z_ele) {
zend_string *zstr = zval_get_string(z_ele);
redis_cmd_append_sstr(&cmdstr, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
zend_string_release(zstr);
} ZEND_HASH_FOREACH_END();
*cmd = cmdstr.c;
*cmd_len = cmdstr.len;
}
/* Any slot will do */
CMD_RAND_SLOT(slot);
return SUCCESS;
}
/*
* Redis commands that don't deal with the server at all. The RedisSock*
* pointer is the only thing retreived differently, so we just take that
* in additon to the standard INTERNAL_FUNCTION_PARAMETERS for arg parsing,
* return value handling, and thread safety. */
void redis_getoption_handler(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, redisCluster *c)
{
zend_long option;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &option)
== FAILURE)
{
RETURN_FALSE;
}
// Return the requested option
switch(option) {
case REDIS_OPT_SERIALIZER:
RETURN_LONG(redis_sock->serializer);
case REDIS_OPT_PREFIX:
if (redis_sock->prefix) {
RETURN_STRINGL(ZSTR_VAL(redis_sock->prefix), ZSTR_LEN(redis_sock->prefix));
}
RETURN_NULL();
case REDIS_OPT_READ_TIMEOUT:
RETURN_DOUBLE(redis_sock->read_timeout);
case REDIS_OPT_SCAN:
RETURN_LONG(redis_sock->scan);
case REDIS_OPT_FAILOVER:
RETURN_LONG(c->failover);
default:
RETURN_FALSE;
}
}
void redis_setoption_handler(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, redisCluster *c)
{
long val_long;
zend_long option;
char *val_str;
struct timeval read_tv;
strlen_t val_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ls", &option,
&val_str, &val_len) == FAILURE)
{
RETURN_FALSE;
}
switch(option) {
case REDIS_OPT_SERIALIZER:
val_long = atol(val_str);
if (val_long == REDIS_SERIALIZER_NONE || val_long == REDIS_SERIALIZER_PHP
#ifdef HAVE_REDIS_IGBINARY
|| val_long == REDIS_SERIALIZER_IGBINARY
#endif
) {
redis_sock->serializer = val_long;
RETURN_TRUE;
}
break;
case REDIS_OPT_PREFIX:
if (redis_sock->prefix) {
zend_string_release(redis_sock->prefix);
redis_sock->prefix = NULL;
}
if (val_str && val_len > 0) {
redis_sock->prefix = zend_string_init(val_str, val_len, 0);
}
RETURN_TRUE;
case REDIS_OPT_READ_TIMEOUT:
redis_sock->read_timeout = atof(val_str);
if(redis_sock->stream) {
read_tv.tv_sec = (time_t)redis_sock->read_timeout;
read_tv.tv_usec = (int)((redis_sock->read_timeout -
read_tv.tv_sec) * 1000000);
php_stream_set_option(redis_sock->stream,
PHP_STREAM_OPTION_READ_TIMEOUT, 0,
&read_tv);
}
RETURN_TRUE;
case REDIS_OPT_SCAN:
val_long = atol(val_str);
if(val_long==REDIS_SCAN_NORETRY || val_long==REDIS_SCAN_RETRY) {
redis_sock->scan = val_long;
RETURN_TRUE;
}
break;
case REDIS_OPT_FAILOVER:
val_long = atol(val_str);
if (val_long == REDIS_FAILOVER_NONE ||
val_long == REDIS_FAILOVER_ERROR ||
val_long == REDIS_FAILOVER_DISTRIBUTE ||
val_long == REDIS_FAILOVER_DISTRIBUTE_SLAVES)
{
c->failover = val_long;
RETURN_TRUE;
}
break;
EMPTY_SWITCH_DEFAULT_CASE()
}
RETURN_FALSE;
}
void redis_prefix_handler(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock) {
char *key;
strlen_t key_len;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_len)
==FAILURE)
{
RETURN_FALSE;
}
if (redis_sock->prefix) {
int keyfree = redis_key_prefix(redis_sock, &key, &key_len);
RETVAL_STRINGL(key, key_len);
if (keyfree) efree(key);
} else {
RETURN_STRINGL(key, key_len);
}
}
void redis_serialize_handler(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock)
{
zval *z_val;
char *val;
strlen_t val_len;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &z_val)==FAILURE) {
RETURN_FALSE;
}
int val_free = redis_serialize(redis_sock, z_val, &val, &val_len TSRMLS_CC);
RETVAL_STRINGL(val, val_len);
if(val_free) efree(val);
}
void redis_unserialize_handler(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, zend_class_entry *ex)
{
char *value;
strlen_t value_len;
// Parse our arguments
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &value, &value_len)
== FAILURE)
{
RETURN_FALSE;
}
// We only need to attempt unserialization if we have a serializer running
if (redis_sock->serializer == REDIS_SERIALIZER_NONE) {
// Just return the value that was passed to us
RETURN_STRINGL(value, value_len);
}
zval zv, *z_ret = &zv;
if (!redis_unserialize(redis_sock, value, value_len, z_ret TSRMLS_CC)) {
// Badly formed input, throw an execption
zend_throw_exception(ex, "Invalid serialized data, or unserialization error", 0 TSRMLS_CC);
RETURN_FALSE;
}
RETURN_ZVAL(z_ret, 1, 0);
}
/* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */
redis-3.1.6/redis_commands.h 0000644 0001750 0000012 00000026570 13223116600 016552 0 ustar pyatsukhnenko wheel #ifndef REDIS_COMMANDS_H
#define REDIS_COMMANDS_H
#include "common.h"
#include "library.h"
#include "cluster_library.h"
/* Pick a random slot, any slot (for stuff like publish/subscribe) */
#define CMD_RAND_SLOT(slot) \
if(slot) *slot = rand() % REDIS_CLUSTER_MOD
/* Macro for setting the slot if we've been asked to */
#define CMD_SET_SLOT(slot,key,key_len) \
if (slot) *slot = cluster_hash_key(key,key_len);
/* Simple container so we can push subscribe context out */
typedef struct subscribeContext {
char *kw;
int argc;
zend_fcall_info cb;
zend_fcall_info_cache cb_cache;
} subscribeContext;
/* Georadius sort type */
typedef enum geoSortType {
SORT_NONE,
SORT_ASC,
SORT_DESC
} geoSortType;
/* Construct a raw command */
int redis_build_raw_cmd(zval *z_args, int argc, char **cmd, int *cmd_len TSRMLS_DC);
/* Redis command generics. Many commands share common prototypes meaning that
* we can write one function to handle all of them. For example, there are
* many COMMAND key value commands, or COMMAND key commands. */
int redis_empty_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_key_long_val_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_key_long_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_kv_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_key_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_key_key_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_key_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_key_long_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_key_long_long_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_key_str_str_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_key_dbl_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_key_varval_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_key_arr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
/* Construct SCAN and similar commands, as well as check iterator */
int redis_scan_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
REDIS_SCAN_TYPE type, char **cmd, int *cmd_len);
/* ZRANGE, ZREVRANGE, ZRANGEBYSCORE, and ZREVRANGEBYSCORE callback type */
typedef int (*zrange_cb)(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *,char**,int*,int*,short*,void**);
int redis_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, int *withscores, short *slot,
void **ctx);
int redis_zrangebyscore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, int *withscores, short *slot,
void **ctx);
int redis_zinter_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_subscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_zrangebylex_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_gen_zlex_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_eval_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char *kw, char **cmd, int *cmd_len, short *slot, void **ctx);
/* Commands which need a unique construction mechanism. This is either because
* they don't share a signature with any other command, or because there is
* specific processing we do (e.g. verifying subarguments) that make them
* unique */
int redis_set_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_brpoplpush_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_incr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_decr_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_hincrby_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_hincrbyfloat_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_hmget_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_hmset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_hstrlen_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_bitop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_bitcount_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_bitpos_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_pfcount_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_pfadd_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_pfmerge_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_auth_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_setbit_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_linsert_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_lrem_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_smove_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_hset_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_hsetnx_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_srandmember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx, short *have_count);
int redis_zincrby_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_sort_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
int *using_store, char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_hdel_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_zadd_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_object_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
REDIS_REPLY_TYPE *rtype, char **cmd, int *cmd_len, short *slot,
void **ctx);
int redis_del_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_watch_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_blpop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_brpop_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_sinter_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_sinterstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_sunion_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_sunionstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_sdiff_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_sdiffstore_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_command_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_fmt_scan_cmd(char **cmd, REDIS_SCAN_TYPE type, char *key, int key_len,
long it, char *pat, int pat_len, long count);
int redis_geodist_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_georadius_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_georadiusbymember_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
int redis_migrate_cmd(INTERNAL_FUNCTION_PARAMETERS, RedisSock *redis_sock,
char **cmd, int *cmd_len, short *slot, void **ctx);
/* Commands that don't communicate with Redis at all (such as getOption,
* setOption, _prefix, _serialize, etc). These can be handled in one place
* with the method of grabbing our RedisSock* object in different ways
* depending if this is a Redis object or a RedisCluster object. */
void redis_getoption_handler(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, redisCluster *c);
void redis_setoption_handler(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, redisCluster *c);
void redis_prefix_handler(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock);
void redis_serialize_handler(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock);
void redis_unserialize_handler(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, zend_class_entry *ex);
#endif
/* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */
redis-3.1.6/redis.c 0000644 0001750 0000012 00000322334 13223116600 014661 0 ustar pyatsukhnenko wheel /* -*- Mode: C; tab-width: 4 -*- */
/*
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2009 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Original author: Alfonso Jimenez |
| Maintainer: Nicolas Favre-Felix |
| Maintainer: Nasreddine Bouafif |
| Maintainer: Michael Grunder |
+----------------------------------------------------------------------+
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "common.h"
#include "ext/standard/info.h"
#include "php_redis.h"
#include "redis_commands.h"
#include "redis_array.h"
#include "redis_cluster.h"
#include
#ifdef PHP_SESSION
#include "ext/session/php_session.h"
#endif
#include "library.h"
#ifdef PHP_SESSION
extern ps_module ps_mod_redis;
extern ps_module ps_mod_redis_cluster;
#endif
extern zend_class_entry *redis_array_ce;
extern zend_class_entry *redis_cluster_ce;
extern zend_class_entry *redis_cluster_exception_ce;
zend_class_entry *redis_ce;
zend_class_entry *redis_exception_ce;
extern zend_function_entry redis_array_functions[];
extern zend_function_entry redis_cluster_functions[];
PHP_INI_BEGIN()
/* redis arrays */
PHP_INI_ENTRY("redis.arrays.names", "", PHP_INI_ALL, NULL)
PHP_INI_ENTRY("redis.arrays.hosts", "", PHP_INI_ALL, NULL)
PHP_INI_ENTRY("redis.arrays.previous", "", PHP_INI_ALL, NULL)
PHP_INI_ENTRY("redis.arrays.functions", "", PHP_INI_ALL, NULL)
PHP_INI_ENTRY("redis.arrays.index", "", PHP_INI_ALL, NULL)
PHP_INI_ENTRY("redis.arrays.autorehash", "", PHP_INI_ALL, NULL)
/* redis cluster */
PHP_INI_ENTRY("redis.clusters.seeds", "", PHP_INI_ALL, NULL)
PHP_INI_ENTRY("redis.clusters.timeout", "", PHP_INI_ALL, NULL)
PHP_INI_ENTRY("redis.clusters.read_timeout", "", PHP_INI_ALL, NULL)
PHP_INI_END()
/** {{{ Argument info for commands in redis 1.0 */
ZEND_BEGIN_ARG_INFO_EX(arginfo_connect, 0, 0, 2)
ZEND_ARG_INFO(0, host)
ZEND_ARG_INFO(0, port)
ZEND_ARG_INFO(0, timeout)
ZEND_ARG_INFO(0, retry_interval)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_pconnect, 0, 0, 2)
ZEND_ARG_INFO(0, host)
ZEND_ARG_INFO(0, port)
ZEND_ARG_INFO(0, timeout)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_mget, 0, 0, 1)
ZEND_ARG_ARRAY_INFO(0, keys, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_del, 0, 0, 1)
ZEND_ARG_INFO(0, key)
#if PHP_VERSION_ID >= 50600
ZEND_ARG_VARIADIC_INFO(0, other_keys)
#else
ZEND_ARG_INFO(0, ...)
#endif
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_keys, 0, 0, 1)
ZEND_ARG_INFO(0, pattern)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_generic_sort, 0, 0, 1)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, pattern)
ZEND_ARG_INFO(0, get)
ZEND_ARG_INFO(0, start)
ZEND_ARG_INFO(0, end)
ZEND_ARG_INFO(0, getList)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_lrem, 0, 0, 3)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, value)
ZEND_ARG_INFO(0, count)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_auth, 0, 0, 1)
ZEND_ARG_INFO(0, password)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_select, 0, 0, 1)
ZEND_ARG_INFO(0, dbindex)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_move, 0, 0, 2)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, dbindex)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_slaveof, 0, 0, 0)
ZEND_ARG_INFO(0, host)
ZEND_ARG_INFO(0, port)
ZEND_END_ARG_INFO()
/* }}} */
ZEND_BEGIN_ARG_INFO_EX(arginfo_migrate, 0, 0, 5)
ZEND_ARG_INFO(0, host)
ZEND_ARG_INFO(0, port)
ZEND_ARG_INFO(0, key)
ZEND_ARG_INFO(0, db)
ZEND_ARG_INFO(0, timeout)
ZEND_ARG_INFO(0, copy)
ZEND_ARG_INFO(0, replace)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_wait, 0, 0, 2)
ZEND_ARG_INFO(0, numslaves)
ZEND_ARG_INFO(0, timeout)
ZEND_END_ARG_INFO()
/**
* Argument info for the SCAN proper
*/
ZEND_BEGIN_ARG_INFO_EX(arginfo_scan, 0, 0, 1)
ZEND_ARG_INFO(1, i_iterator)
ZEND_ARG_INFO(0, str_pattern)
ZEND_ARG_INFO(0, i_count)
ZEND_END_ARG_INFO()
/**
* Argument info for key scanning
*/
ZEND_BEGIN_ARG_INFO_EX(arginfo_kscan, 0, 0, 2)
ZEND_ARG_INFO(0, str_key)
ZEND_ARG_INFO(1, i_iterator)
ZEND_ARG_INFO(0, str_pattern)
ZEND_ARG_INFO(0, i_count)
ZEND_END_ARG_INFO()
#ifdef ZTS
ZEND_DECLARE_MODULE_GLOBALS(redis)
#endif
static zend_function_entry redis_functions[] = {
PHP_ME(Redis, __construct, NULL, ZEND_ACC_CTOR | ZEND_ACC_PUBLIC)
PHP_ME(Redis, __destruct, NULL, ZEND_ACC_DTOR | ZEND_ACC_PUBLIC)
PHP_ME(Redis, connect, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, pconnect, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, close, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, ping, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, echo, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, get, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, set, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, setex, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, psetex, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, setnx, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, getSet, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, randomKey, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, renameKey, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, renameNx, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, getMultiple, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, exists, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, delete, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, incr, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, incrBy, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, incrByFloat, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, decr, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, decrBy, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, type, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, append, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, getRange, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, setRange, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, getBit, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, setBit, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, strlen, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, getKeys, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sort, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sortAsc, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sortAscAlpha, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sortDesc, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sortDescAlpha, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, lPush, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, rPush, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, lPushx, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, rPushx, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, lPop, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, rPop, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, blPop, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, brPop, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, lSize, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, lRemove, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, listTrim, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, lGet, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, lGetRange, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, lSet, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, lInsert, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sAdd, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sAddArray, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sSize, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sRemove, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sMove, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sPop, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sRandMember, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sContains, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sMembers, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sInter, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sInterStore, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sUnion, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sUnionStore, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sDiff, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sDiffStore, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, setTimeout, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, save, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, bgSave, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, lastSave, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, flushDB, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, flushAll, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, dbSize, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, auth, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, ttl, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, pttl, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, persist, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, info, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, select, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, move, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, bgrewriteaof, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, slaveof, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, object, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, bitop, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, bitcount, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, bitpos, NULL, ZEND_ACC_PUBLIC)
/* 1.1 */
PHP_ME(Redis, mset, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, msetnx, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, rpoplpush, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, brpoplpush, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zAdd, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zDelete, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zRange, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zRevRange, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zRangeByScore, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zRevRangeByScore, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zRangeByLex, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zRevRangeByLex, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zLexCount, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zRemRangeByLex, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zCount, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zDeleteRangeByScore, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zDeleteRangeByRank, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zCard, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zScore, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zRank, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zRevRank, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zInter, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zUnion, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zIncrBy, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, expireAt, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, pexpire, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, pexpireAt, NULL, ZEND_ACC_PUBLIC)
/* 1.2 */
PHP_ME(Redis, hGet, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, hSet, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, hSetNx, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, hDel, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, hLen, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, hKeys, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, hVals, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, hGetAll, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, hExists, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, hIncrBy, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, hIncrByFloat, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, hMset, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, hMget, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, hStrLen, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, multi, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, discard, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, exec, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, pipeline, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, watch, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, unwatch, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, publish, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, subscribe, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, psubscribe, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, unsubscribe, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, punsubscribe, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, time, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, role, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, eval, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, evalsha, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, script, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, debug, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, dump, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, restore, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, migrate, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, getLastError, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, clearLastError, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, _prefix, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, _serialize, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, _unserialize, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, client, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, command, NULL, ZEND_ACC_PUBLIC)
/* SCAN and friends */
PHP_ME(Redis, scan, arginfo_scan, ZEND_ACC_PUBLIC)
PHP_ME(Redis, hscan, arginfo_kscan, ZEND_ACC_PUBLIC)
PHP_ME(Redis, zscan, arginfo_kscan, ZEND_ACC_PUBLIC)
PHP_ME(Redis, sscan, arginfo_kscan, ZEND_ACC_PUBLIC)
/* HyperLogLog commands */
PHP_ME(Redis, pfadd, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, pfcount, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, pfmerge, NULL, ZEND_ACC_PUBLIC)
/* options */
PHP_ME(Redis, getOption, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, setOption, NULL, ZEND_ACC_PUBLIC)
/* config */
PHP_ME(Redis, config, NULL, ZEND_ACC_PUBLIC)
/* slowlog */
PHP_ME(Redis, slowlog, NULL, ZEND_ACC_PUBLIC)
/* Send a raw command and read raw results */
PHP_ME(Redis, rawcommand, NULL, ZEND_ACC_PUBLIC)
/* geoadd and friends */
PHP_ME(Redis, geoadd, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, geohash, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, geopos, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, geodist, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, georadius, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, georadiusbymember, NULL, ZEND_ACC_PUBLIC)
/* introspection */
PHP_ME(Redis, getHost, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, getPort, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, getDBNum, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, getTimeout, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, getReadTimeout, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, getPersistentID, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, getAuth, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, isConnected, NULL, ZEND_ACC_PUBLIC)
/* TODO: document getMode() and wait() in README? */
PHP_ME(Redis, getMode, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, wait, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Redis, pubsub, NULL, ZEND_ACC_PUBLIC)
/* aliases */
PHP_MALIAS(Redis, open, connect, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, popen, pconnect, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, lLen, lSize, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, sGetMembers, sMembers, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, mget, getMultiple, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, expire, setTimeout, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, zunionstore, zUnion, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, zinterstore, zInter, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, zRemove, zDelete, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, zRem, zDelete, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, zRemoveRangeByScore, zDeleteRangeByScore, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, zRemRangeByScore, zDeleteRangeByScore, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, zRemRangeByRank, zDeleteRangeByRank, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, zSize, zCard, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, substr, getRange, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, rename, renameKey, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, del, delete, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, keys, getKeys, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, lrem, lRemove, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, ltrim, listTrim, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, lindex, lGet, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, lrange, lGetRange, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, scard, sSize, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, srem, sRemove, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, sismember, sContains, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, zReverseRange, zRevRange, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, sendEcho, echo, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, evaluate, eval, NULL, ZEND_ACC_PUBLIC)
PHP_MALIAS(Redis, evaluateSha, evalsha, NULL, ZEND_ACC_PUBLIC)
PHP_FE_END
};
static const zend_module_dep redis_deps[] = {
#ifdef HAVE_REDIS_IGBINARY
ZEND_MOD_REQUIRED("igbinary")
#endif
ZEND_MOD_END
};
zend_module_entry redis_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER_EX,
NULL,
redis_deps,
#endif
"redis",
NULL,
PHP_MINIT(redis),
PHP_MSHUTDOWN(redis),
NULL,
NULL,
PHP_MINFO(redis),
#if ZEND_MODULE_API_NO >= 20010901
PHP_REDIS_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_REDIS
ZEND_GET_MODULE(redis)
#endif
/* Send a static DISCARD in case we're in MULTI mode. */
static int
redis_send_discard(RedisSock *redis_sock TSRMLS_DC)
{
int result = FAILURE;
char *cmd, *resp;
int resp_len, cmd_len;
/* format our discard command */
cmd_len = REDIS_SPPRINTF(&cmd, "DISCARD", "");
/* send our DISCARD command */
if (redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) >= 0 &&
(resp = redis_sock_read(redis_sock,&resp_len TSRMLS_CC)) != NULL)
{
/* success if we get OK */
result = (resp_len == 3 && strncmp(resp,"+OK", 3)==0) ? SUCCESS:FAILURE;
/* free our response */
efree(resp);
}
/* free our command */
efree(cmd);
/* return success/failure */
return result;
}
static void
free_reply_callbacks(RedisSock *redis_sock)
{
fold_item *fi;
for (fi = redis_sock->head; fi; ) {
fold_item *fi_next = fi->next;
free(fi);
fi = fi_next;
}
redis_sock->head = NULL;
redis_sock->current = NULL;
}
#if (PHP_MAJOR_VERSION < 7)
void
free_redis_object(void *object TSRMLS_DC)
{
redis_object *redis = (redis_object *)object;
zend_object_std_dtor(&redis->std TSRMLS_CC);
if (redis->sock) {
redis_sock_disconnect(redis->sock TSRMLS_CC);
redis_free_socket(redis->sock);
}
efree(redis);
}
zend_object_value
create_redis_object(zend_class_entry *ce TSRMLS_DC)
{
zend_object_value retval;
redis_object *redis = ecalloc(1, sizeof(redis_object));
memset(redis, 0, sizeof(redis_object));
zend_object_std_init(&redis->std, ce TSRMLS_CC);
#if PHP_VERSION_ID < 50399
zval *tmp;
zend_hash_copy(redis->std.properties, &ce->default_properties,
(copy_ctor_func_t)zval_add_ref, (void *)&tmp, sizeof(zval *));
#else
object_properties_init(&redis->std, ce);
#endif
retval.handle = zend_objects_store_put(redis,
(zend_objects_store_dtor_t)zend_objects_destroy_object,
(zend_objects_free_object_storage_t)free_redis_object,
NULL TSRMLS_CC);
retval.handlers = zend_get_std_object_handlers();
return retval;
}
#else
zend_object_handlers redis_object_handlers;
void
free_redis_object(zend_object *object)
{
redis_object *redis = (redis_object *)((char *)(object) - XtOffsetOf(redis_object, std));
zend_object_std_dtor(&redis->std TSRMLS_CC);
if (redis->sock) {
redis_sock_disconnect(redis->sock TSRMLS_CC);
redis_free_socket(redis->sock);
}
}
zend_object *
create_redis_object(zend_class_entry *ce TSRMLS_DC)
{
redis_object *redis = ecalloc(1, sizeof(redis_object) + zend_object_properties_size(ce));
redis->sock = NULL;
zend_object_std_init(&redis->std, ce TSRMLS_CC);
object_properties_init(&redis->std, ce);
memcpy(&redis_object_handlers, zend_get_std_object_handlers(), sizeof(redis_object_handlers));
redis_object_handlers.offset = XtOffsetOf(redis_object, std);
redis_object_handlers.free_obj = free_redis_object;
redis->std.handlers = &redis_object_handlers;
return &redis->std;
}
#endif
static zend_always_inline RedisSock *
redis_sock_get_instance(zval *id TSRMLS_DC, int no_throw)
{
redis_object *redis;
if (Z_TYPE_P(id) == IS_OBJECT) {
#if (PHP_MAJOR_VERSION < 7)
redis = (redis_object *)zend_objects_get_address(id TSRMLS_CC);
#else
redis = (redis_object *)((char *)Z_OBJ_P(id) - XtOffsetOf(redis_object, std));
#endif
if (redis->sock) {
return redis->sock;
}
}
// Throw an exception unless we've been requested not to
if (!no_throw) {
zend_throw_exception(redis_exception_ce, "Redis server went away", 0 TSRMLS_CC);
}
return NULL;
}
/**
* redis_sock_get
*/
PHP_REDIS_API RedisSock *
redis_sock_get(zval *id TSRMLS_DC, int no_throw)
{
RedisSock *redis_sock;
if ((redis_sock = redis_sock_get_instance(id TSRMLS_CC, no_throw)) == NULL) {
return NULL;
}
if (redis_sock->lazy_connect) {
redis_sock->lazy_connect = 0;
if (redis_sock_server_open(redis_sock TSRMLS_CC) < 0) {
return NULL;
}
}
return redis_sock;
}
/**
* redis_sock_get_direct
* Returns our attached RedisSock pointer if we're connected
*/
PHP_REDIS_API RedisSock *redis_sock_get_connected(INTERNAL_FUNCTION_PARAMETERS) {
zval *object;
RedisSock *redis_sock;
// If we can't grab our object, or get a socket, or we're not connected,
// return NULL
if((zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O",
&object, redis_ce) == FAILURE) ||
(redis_sock = redis_sock_get(object TSRMLS_CC, 1)) == NULL ||
redis_sock->status != REDIS_SOCK_STATUS_CONNECTED)
{
return NULL;
}
/* Return our socket */
return redis_sock;
}
/* Redis and RedisCluster objects share serialization/prefixing settings so
* this is a generic function to add class constants to either */
static void add_class_constants(zend_class_entry *ce, int is_cluster TSRMLS_DC) {
zend_declare_class_constant_long(ce, ZEND_STRL("REDIS_NOT_FOUND"), REDIS_NOT_FOUND TSRMLS_CC);
zend_declare_class_constant_long(ce, ZEND_STRL("REDIS_STRING"), REDIS_STRING TSRMLS_CC);
zend_declare_class_constant_long(ce, ZEND_STRL("REDIS_SET"), REDIS_SET TSRMLS_CC);
zend_declare_class_constant_long(ce, ZEND_STRL("REDIS_LIST"), REDIS_LIST TSRMLS_CC);
zend_declare_class_constant_long(ce, ZEND_STRL("REDIS_ZSET"), REDIS_ZSET TSRMLS_CC);
zend_declare_class_constant_long(ce, ZEND_STRL("REDIS_HASH"), REDIS_HASH TSRMLS_CC);
/* Cluster doesn't support pipelining at this time */
if(!is_cluster) {
zend_declare_class_constant_long(ce, ZEND_STRL("PIPELINE"), PIPELINE TSRMLS_CC);
}
/* Add common mode constants */
zend_declare_class_constant_long(ce, ZEND_STRL("ATOMIC"), ATOMIC TSRMLS_CC);
zend_declare_class_constant_long(ce, ZEND_STRL("MULTI"), MULTI TSRMLS_CC);
/* options */
zend_declare_class_constant_long(ce, ZEND_STRL("OPT_SERIALIZER"), REDIS_OPT_SERIALIZER TSRMLS_CC);
zend_declare_class_constant_long(ce, ZEND_STRL("OPT_PREFIX"), REDIS_OPT_PREFIX TSRMLS_CC);
zend_declare_class_constant_long(ce, ZEND_STRL("OPT_READ_TIMEOUT"), REDIS_OPT_READ_TIMEOUT TSRMLS_CC);
/* serializer */
zend_declare_class_constant_long(ce, ZEND_STRL("SERIALIZER_NONE"), REDIS_SERIALIZER_NONE TSRMLS_CC);
zend_declare_class_constant_long(ce, ZEND_STRL("SERIALIZER_PHP"), REDIS_SERIALIZER_PHP TSRMLS_CC);
#ifdef HAVE_REDIS_IGBINARY
zend_declare_class_constant_long(ce, ZEND_STRL("SERIALIZER_IGBINARY"), REDIS_SERIALIZER_IGBINARY TSRMLS_CC);
#endif
/* scan options*/
zend_declare_class_constant_long(ce, ZEND_STRL("OPT_SCAN"), REDIS_OPT_SCAN TSRMLS_CC);
zend_declare_class_constant_long(ce, ZEND_STRL("SCAN_RETRY"), REDIS_SCAN_RETRY TSRMLS_CC);
zend_declare_class_constant_long(ce, ZEND_STRL("SCAN_NORETRY"), REDIS_SCAN_NORETRY TSRMLS_CC);
/* Cluster option to allow for slave failover */
if (is_cluster) {
zend_declare_class_constant_long(ce, ZEND_STRL("OPT_SLAVE_FAILOVER"), REDIS_OPT_FAILOVER TSRMLS_CC);
zend_declare_class_constant_long(ce, ZEND_STRL("FAILOVER_NONE"), REDIS_FAILOVER_NONE TSRMLS_CC);
zend_declare_class_constant_long(ce, ZEND_STRL("FAILOVER_ERROR"), REDIS_FAILOVER_ERROR TSRMLS_CC);
zend_declare_class_constant_long(ce, ZEND_STRL("FAILOVER_DISTRIBUTE"), REDIS_FAILOVER_DISTRIBUTE TSRMLS_CC);
zend_declare_class_constant_long(ce, ZEND_STRL("FAILOVER_DISTRIBUTE_SLAVES"), REDIS_FAILOVER_DISTRIBUTE_SLAVES TSRMLS_CC);
}
zend_declare_class_constant_stringl(ce, "AFTER", 5, "after", 5 TSRMLS_CC);
zend_declare_class_constant_stringl(ce, "BEFORE", 6, "before", 6 TSRMLS_CC);
}
/**
* PHP_MINIT_FUNCTION
*/
PHP_MINIT_FUNCTION(redis)
{
struct timeval tv;
zend_class_entry redis_class_entry;
zend_class_entry redis_array_class_entry;
zend_class_entry redis_cluster_class_entry;
zend_class_entry redis_exception_class_entry;
zend_class_entry redis_cluster_exception_class_entry;
zend_class_entry *exception_ce = NULL;
/* Seed random generator (for RedisCluster failover) */
gettimeofday(&tv, NULL);
srand(tv.tv_usec * tv.tv_sec);
REGISTER_INI_ENTRIES();
/* Redis class */
INIT_CLASS_ENTRY(redis_class_entry, "Redis", redis_functions);
redis_ce = zend_register_internal_class(&redis_class_entry TSRMLS_CC);
redis_ce->create_object = create_redis_object;
/* RedisArray class */
INIT_CLASS_ENTRY(redis_array_class_entry, "RedisArray", redis_array_functions);
redis_array_ce = zend_register_internal_class(&redis_array_class_entry TSRMLS_CC);
redis_array_ce->create_object = create_redis_array_object;
/* RedisCluster class */
INIT_CLASS_ENTRY(redis_cluster_class_entry, "RedisCluster", redis_cluster_functions);
redis_cluster_ce = zend_register_internal_class(&redis_cluster_class_entry TSRMLS_CC);
redis_cluster_ce->create_object = create_cluster_context;
/* Base Exception class */
#if HAVE_SPL
exception_ce = zend_hash_str_find_ptr(CG(class_table), "RuntimeException", sizeof("RuntimeException") - 1);
#endif
if (exception_ce == NULL) {
#if (PHP_MAJOR_VERSION == 5) && (PHP_MINOR_VERSION < 2)
exception_ce = zend_exception_get_default();
#else
exception_ce = zend_exception_get_default(TSRMLS_C);
#endif
}
/* RedisException class */
INIT_CLASS_ENTRY(redis_exception_class_entry, "RedisException", NULL);
redis_exception_ce = zend_register_internal_class_ex(
&redis_exception_class_entry,
#if (PHP_MAJOR_VERSION < 7)
exception_ce, NULL TSRMLS_CC
#else
exception_ce
#endif
);
/* RedisClusterException class */
INIT_CLASS_ENTRY(redis_cluster_exception_class_entry,
"RedisClusterException", NULL);
redis_cluster_exception_ce = zend_register_internal_class_ex(
&redis_cluster_exception_class_entry,
#if (PHP_MAJOR_VERSION < 7)
exception_ce, NULL TSRMLS_CC
#else
exception_ce
#endif
);
/* Add shared class constants to Redis and RedisCluster objects */
add_class_constants(redis_ce, 0 TSRMLS_CC);
add_class_constants(redis_cluster_ce, 1 TSRMLS_CC);
#ifdef PHP_SESSION
php_session_register_module(&ps_mod_redis);
php_session_register_module(&ps_mod_redis_cluster);
#endif
return SUCCESS;
}
/**
* PHP_MSHUTDOWN_FUNCTION
*/
PHP_MSHUTDOWN_FUNCTION(redis)
{
return SUCCESS;
}
/**
* PHP_MINFO_FUNCTION
*/
PHP_MINFO_FUNCTION(redis)
{
php_info_print_table_start();
php_info_print_table_header(2, "Redis Support", "enabled");
php_info_print_table_row(2, "Redis Version", PHP_REDIS_VERSION);
#ifdef HAVE_REDIS_IGBINARY
php_info_print_table_row(2, "Available serializers", "php, igbinary");
#else
php_info_print_table_row(2, "Available serializers", "php");
#endif
php_info_print_table_end();
}
/* {{{ proto Redis Redis::__construct()
Public constructor */
PHP_METHOD(Redis, __construct)
{
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "") == FAILURE) {
RETURN_FALSE;
}
}
/* }}} */
/* {{{ proto Redis Redis::__destruct()
Public Destructor
*/
PHP_METHOD(Redis,__destruct) {
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "") == FAILURE) {
RETURN_FALSE;
}
// Grab our socket
RedisSock *redis_sock;
if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 1)) == NULL) {
RETURN_FALSE;
}
// If we think we're in MULTI mode, send a discard
IF_MULTI() {
IF_NOT_PIPELINE() {
// Discard any multi commands, and free any callbacks that have been
// queued
redis_send_discard(redis_sock TSRMLS_CC);
}
free_reply_callbacks(redis_sock);
}
}
/* {{{ proto boolean Redis::connect(string host, int port [, double timeout [, long retry_interval]])
*/
PHP_METHOD(Redis, connect)
{
if (redis_connect(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0) == FAILURE) {
RETURN_FALSE;
} else {
RETURN_TRUE;
}
}
/* }}} */
/* {{{ proto boolean Redis::pconnect(string host, int port [, double timeout])
*/
PHP_METHOD(Redis, pconnect)
{
if (redis_connect(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1) == FAILURE) {
RETURN_FALSE;
} else {
RETURN_TRUE;
}
}
/* }}} */
PHP_REDIS_API int
redis_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent)
{
zval *object;
char *host = NULL, *persistent_id = "";
zend_long port = -1, retry_interval = 0;
strlen_t host_len, persistent_id_len;
double timeout = 0.0, read_timeout = 0.0;
redis_object *redis;
#ifdef ZTS
/* not sure how in threaded mode this works so disabled persistence at
* first */
persistent = 0;
#endif
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(),
"Os|ldsld", &object, redis_ce, &host,
&host_len, &port, &timeout, &persistent_id,
&persistent_id_len, &retry_interval,
&read_timeout) == FAILURE)
{
return FAILURE;
} else if (!persistent) {
persistent_id = NULL;
}
if (timeout < 0L || timeout > INT_MAX) {
zend_throw_exception(redis_exception_ce,
"Invalid connect timeout", 0 TSRMLS_CC);
return FAILURE;
}
if (read_timeout < 0L || read_timeout > INT_MAX) {
zend_throw_exception(redis_exception_ce,
"Invalid read timeout", 0 TSRMLS_CC);
return FAILURE;
}
if (retry_interval < 0L || retry_interval > INT_MAX) {
zend_throw_exception(redis_exception_ce, "Invalid retry interval",
0 TSRMLS_CC);
return FAILURE;
}
/* If it's not a unix socket, set to default */
if(port == -1 && host_len && host[0] != '/') {
port = 6379;
}
#if (PHP_MAJOR_VERSION < 7)
redis = (redis_object *)zend_objects_get_address(object TSRMLS_CC);
#else
redis = (redis_object *)((char *)Z_OBJ_P(object) - XtOffsetOf(redis_object, std));
#endif
/* if there is a redis sock already we have to remove it */
if (redis->sock) {
redis_sock_disconnect(redis->sock TSRMLS_CC);
redis_free_socket(redis->sock);
}
redis->sock = redis_sock_create(host, host_len, port, timeout, read_timeout, persistent,
persistent_id, retry_interval, 0);
if (redis_sock_server_open(redis->sock TSRMLS_CC) < 0) {
redis_free_socket(redis->sock);
redis->sock = NULL;
return FAILURE;
}
return SUCCESS;
}
/* {{{ proto long Redis::bitop(string op, string key, ...) */
PHP_METHOD(Redis, bitop)
{
REDIS_PROCESS_CMD(bitop, redis_long_response);
}
/* }}} */
/* {{{ proto long Redis::bitcount(string key, [int start], [int end])
*/
PHP_METHOD(Redis, bitcount)
{
REDIS_PROCESS_CMD(bitcount, redis_long_response);
}
/* }}} */
/* {{{ proto integer Redis::bitpos(string key, int bit, [int start, int end]) */
PHP_METHOD(Redis, bitpos)
{
REDIS_PROCESS_CMD(bitpos, redis_long_response);
}
/* }}} */
/* {{{ proto boolean Redis::close()
*/
PHP_METHOD(Redis, close)
{
RedisSock *redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU);
if (redis_sock && redis_sock_disconnect(redis_sock TSRMLS_CC)) {
RETURN_TRUE;
}
RETURN_FALSE;
}
/* }}} */
/* {{{ proto boolean Redis::set(string key, mixed val, long timeout,
* [array opt) */
PHP_METHOD(Redis, set) {
REDIS_PROCESS_CMD(set, redis_boolean_response);
}
/* {{{ proto boolean Redis::setex(string key, long expire, string value)
*/
PHP_METHOD(Redis, setex)
{
REDIS_PROCESS_KW_CMD("SETEX", redis_key_long_val_cmd, redis_boolean_response);
}
/* {{{ proto boolean Redis::psetex(string key, long expire, string value)
*/
PHP_METHOD(Redis, psetex)
{
REDIS_PROCESS_KW_CMD("PSETEX", redis_key_long_val_cmd, redis_boolean_response);
}
/* {{{ proto boolean Redis::setnx(string key, string value)
*/
PHP_METHOD(Redis, setnx)
{
REDIS_PROCESS_KW_CMD("SETNX", redis_kv_cmd, redis_1_response);
}
/* }}} */
/* {{{ proto string Redis::getSet(string key, string value)
*/
PHP_METHOD(Redis, getSet)
{
REDIS_PROCESS_KW_CMD("GETSET", redis_kv_cmd, redis_string_response);
}
/* }}} */
/* {{{ proto string Redis::randomKey()
*/
PHP_METHOD(Redis, randomKey)
{
REDIS_PROCESS_KW_CMD("RANDOMKEY", redis_empty_cmd, redis_ping_response);
}
/* }}} */
/* {{{ proto string Redis::echo(string msg)
*/
PHP_METHOD(Redis, echo)
{
REDIS_PROCESS_KW_CMD("ECHO", redis_str_cmd, redis_string_response);
}
/* }}} */
/* {{{ proto string Redis::renameKey(string key_src, string key_dst)
*/
PHP_METHOD(Redis, renameKey)
{
REDIS_PROCESS_KW_CMD("RENAME", redis_key_key_cmd, redis_boolean_response);
}
/* }}} */
/* {{{ proto string Redis::renameNx(string key_src, string key_dst)
*/
PHP_METHOD(Redis, renameNx)
{
REDIS_PROCESS_KW_CMD("RENAMENX", redis_key_key_cmd, redis_1_response);
}
/* }}} */
/* }}} */
/* {{{ proto string Redis::get(string key)
*/
PHP_METHOD(Redis, get)
{
REDIS_PROCESS_KW_CMD("GET", redis_key_cmd, redis_string_response);
}
/* }}} */
/* {{{ proto string Redis::ping()
*/
PHP_METHOD(Redis, ping)
{
REDIS_PROCESS_KW_CMD("PING", redis_empty_cmd, redis_ping_response);
}
/* }}} */
/* {{{ proto boolean Redis::incr(string key [,int value])
*/
PHP_METHOD(Redis, incr){
REDIS_PROCESS_CMD(incr, redis_long_response);
}
/* }}} */
/* {{{ proto boolean Redis::incrBy(string key ,int value)
*/
PHP_METHOD(Redis, incrBy){
REDIS_PROCESS_KW_CMD("INCRBY", redis_key_long_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto float Redis::incrByFloat(string key, float value)
*/
PHP_METHOD(Redis, incrByFloat) {
REDIS_PROCESS_KW_CMD("INCRBYFLOAT", redis_key_dbl_cmd,
redis_bulk_double_response);
}
/* }}} */
/* {{{ proto boolean Redis::decr(string key) */
PHP_METHOD(Redis, decr)
{
REDIS_PROCESS_CMD(decr, redis_long_response);
}
/* }}} */
/* {{{ proto boolean Redis::decrBy(string key ,int value)
*/
PHP_METHOD(Redis, decrBy){
REDIS_PROCESS_KW_CMD("DECRBY", redis_key_long_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto array Redis::getMultiple(array keys)
*/
PHP_METHOD(Redis, getMultiple)
{
zval *object, *z_args, *z_ele;
HashTable *hash;
RedisSock *redis_sock;
smart_string cmd = {0};
int arg_count;
/* Make sure we have proper arguments */
if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa",
&object, redis_ce, &z_args) == FAILURE) {
RETURN_FALSE;
}
/* We'll need the socket */
if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
/* Grab our array */
hash = Z_ARRVAL_P(z_args);
/* We don't need to do anything if there aren't any keys */
if((arg_count = zend_hash_num_elements(hash)) == 0) {
RETURN_FALSE;
}
/* Build our command header */
redis_cmd_init_sstr(&cmd, arg_count, "MGET", 4);
/* Iterate through and grab our keys */
ZEND_HASH_FOREACH_VAL(hash, z_ele) {
zend_string *zstr = zval_get_string(z_ele);
redis_cmd_append_sstr_key(&cmd, ZSTR_VAL(zstr), ZSTR_LEN(zstr), redis_sock, NULL);
zend_string_release(zstr);
} ZEND_HASH_FOREACH_END();
/* Kick off our command */
REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len);
IF_ATOMIC() {
if(redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,
redis_sock, NULL, NULL) < 0) {
RETURN_FALSE;
}
}
REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply);
}
/* {{{ proto boolean Redis::exists(string key)
*/
PHP_METHOD(Redis, exists)
{
REDIS_PROCESS_KW_CMD("EXISTS", redis_key_cmd, redis_1_response);
}
/* }}} */
/* {{{ proto boolean Redis::delete(string key)
*/
PHP_METHOD(Redis, delete)
{
REDIS_PROCESS_CMD(del, redis_long_response);
}
/* }}} */
PHP_REDIS_API void redis_set_watch(RedisSock *redis_sock)
{
redis_sock->watching = 1;
}
PHP_REDIS_API void redis_watch_response(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, zval *z_tab, void *ctx)
{
redis_boolean_response_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
z_tab, ctx, redis_set_watch);
}
/* {{{ proto boolean Redis::watch(string key1, string key2...)
*/
PHP_METHOD(Redis, watch)
{
REDIS_PROCESS_CMD(watch, redis_watch_response);
}
/* }}} */
PHP_REDIS_API void redis_clear_watch(RedisSock *redis_sock)
{
redis_sock->watching = 0;
}
PHP_REDIS_API void redis_unwatch_response(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, zval *z_tab,
void *ctx)
{
redis_boolean_response_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
z_tab, ctx, redis_clear_watch);
}
/* {{{ proto boolean Redis::unwatch()
*/
PHP_METHOD(Redis, unwatch)
{
REDIS_PROCESS_KW_CMD("UNWATCH", redis_empty_cmd, redis_unwatch_response);
}
/* }}} */
/* {{{ proto array Redis::getKeys(string pattern)
*/
PHP_METHOD(Redis, getKeys)
{
REDIS_PROCESS_KW_CMD("KEYS", redis_key_cmd, redis_mbulk_reply_raw);
}
/* }}} */
/* {{{ proto int Redis::type(string key)
*/
PHP_METHOD(Redis, type)
{
REDIS_PROCESS_KW_CMD("TYPE", redis_key_cmd, redis_type_response);
}
/* }}} */
/* {{{ proto long Redis::append(string key, string val) */
PHP_METHOD(Redis, append)
{
REDIS_PROCESS_KW_CMD("APPEND", redis_kv_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto string Redis::GetRange(string key, long start, long end) */
PHP_METHOD(Redis, getRange)
{
REDIS_PROCESS_KW_CMD("GETRANGE", redis_key_long_long_cmd,
redis_string_response);
}
/* }}} */
/* {{{ proto string Redis::setRange(string key, long start, string value) */
PHP_METHOD(Redis, setRange)
{
REDIS_PROCESS_KW_CMD("SETRANGE", redis_key_long_str_cmd,
redis_long_response);
}
/* }}} */
/* {{{ proto long Redis::getbit(string key, long idx) */
PHP_METHOD(Redis, getBit)
{
REDIS_PROCESS_KW_CMD("GETBIT", redis_key_long_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto long Redis::setbit(string key, long idx, bool|int value) */
PHP_METHOD(Redis, setBit)
{
REDIS_PROCESS_CMD(setbit, redis_long_response);
}
/* }}} */
/* {{{ proto long Redis::strlen(string key) */
PHP_METHOD(Redis, strlen)
{
REDIS_PROCESS_KW_CMD("STRLEN", redis_key_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto boolean Redis::lPush(string key , string value)
*/
PHP_METHOD(Redis, lPush)
{
REDIS_PROCESS_KW_CMD("LPUSH", redis_key_varval_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto boolean Redis::rPush(string key , string value)
*/
PHP_METHOD(Redis, rPush)
{
REDIS_PROCESS_KW_CMD("RPUSH", redis_key_varval_cmd, redis_long_response);
}
/* }}} */
PHP_METHOD(Redis, lInsert)
{
REDIS_PROCESS_CMD(linsert, redis_long_response);
}
/* {{{ proto long Redis::lPushx(string key, mixed value) */
PHP_METHOD(Redis, lPushx)
{
REDIS_PROCESS_KW_CMD("LPUSHX", redis_kv_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto long Redis::rPushx(string key, mixed value) */
PHP_METHOD(Redis, rPushx)
{
REDIS_PROCESS_KW_CMD("RPUSHX", redis_kv_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto string Redis::lPOP(string key) */
PHP_METHOD(Redis, lPop)
{
REDIS_PROCESS_KW_CMD("LPOP", redis_key_cmd, redis_string_response);
}
/* }}} */
/* {{{ proto string Redis::rPOP(string key) */
PHP_METHOD(Redis, rPop)
{
REDIS_PROCESS_KW_CMD("RPOP", redis_key_cmd, redis_string_response);
}
/* }}} */
/* {{{ proto string Redis::blPop(string key1, string key2, ..., int timeout) */
PHP_METHOD(Redis, blPop)
{
REDIS_PROCESS_CMD(blpop, redis_sock_read_multibulk_reply);
}
/* }}} */
/* {{{ proto string Redis::brPop(string key1, string key2, ..., int timeout) */
PHP_METHOD(Redis, brPop)
{
REDIS_PROCESS_CMD(brpop, redis_sock_read_multibulk_reply);
}
/* }}} */
/* {{{ proto int Redis::lSize(string key) */
PHP_METHOD(Redis, lSize)
{
REDIS_PROCESS_KW_CMD("LLEN", redis_key_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto boolean Redis::lRemove(string list, string value, int count = 0) */
PHP_METHOD(Redis, lRemove)
{
REDIS_PROCESS_CMD(lrem, redis_long_response);
}
/* }}} */
/* {{{ proto boolean Redis::listTrim(string key , int start , int end) */
PHP_METHOD(Redis, listTrim)
{
REDIS_PROCESS_KW_CMD("LTRIM", redis_key_long_long_cmd,
redis_boolean_response);
}
/* }}} */
/* {{{ proto string Redis::lGet(string key , int index) */
PHP_METHOD(Redis, lGet)
{
REDIS_PROCESS_KW_CMD("LINDEX", redis_key_long_cmd, redis_string_response);
}
/* }}} */
/* {{{ proto array Redis::lGetRange(string key, int start , int end) */
PHP_METHOD(Redis, lGetRange)
{
REDIS_PROCESS_KW_CMD("LRANGE", redis_key_long_long_cmd,
redis_sock_read_multibulk_reply);
}
/* }}} */
/* {{{ proto long Redis::sAdd(string key , mixed value) */
PHP_METHOD(Redis, sAdd)
{
REDIS_PROCESS_KW_CMD("SADD", redis_key_varval_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto boolean Redis::sAddArray(string key, array $values) */
PHP_METHOD(Redis, sAddArray) {
REDIS_PROCESS_KW_CMD("SADD", redis_key_arr_cmd, redis_long_response);
} /* }}} */
/* {{{ proto int Redis::sSize(string key) */
PHP_METHOD(Redis, sSize)
{
REDIS_PROCESS_KW_CMD("SCARD", redis_key_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto boolean Redis::sRemove(string set, string value) */
PHP_METHOD(Redis, sRemove)
{
REDIS_PROCESS_KW_CMD("SREM", redis_key_varval_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto boolean Redis::sMove(string src, string dst, mixed value) */
PHP_METHOD(Redis, sMove)
{
REDIS_PROCESS_CMD(smove, redis_1_response);
}
/* }}} */
/* {{{ proto string Redis::sPop(string key) */
PHP_METHOD(Redis, sPop)
{
if (ZEND_NUM_ARGS() == 1) {
REDIS_PROCESS_KW_CMD("SPOP", redis_key_cmd, redis_string_response);
} else if (ZEND_NUM_ARGS() == 2) {
REDIS_PROCESS_KW_CMD("SPOP", redis_key_long_cmd, redis_sock_read_multibulk_reply);
} else {
ZEND_WRONG_PARAM_COUNT();
}
}
/* }}} */
/* {{{ proto string Redis::sRandMember(string key [int count]) */
PHP_METHOD(Redis, sRandMember)
{
char *cmd;
int cmd_len;
short have_count;
RedisSock *redis_sock;
// Grab our socket, validate call
if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL ||
redis_srandmember_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
&cmd, &cmd_len, NULL, NULL, &have_count)==FAILURE)
{
RETURN_FALSE;
}
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
if(have_count) {
IF_ATOMIC() {
if(redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,
redis_sock, NULL, NULL)<0)
{
RETURN_FALSE;
}
}
REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply);
} else {
IF_ATOMIC() {
redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
NULL, NULL);
}
REDIS_PROCESS_RESPONSE(redis_string_response);
}
}
/* }}} */
/* {{{ proto boolean Redis::sContains(string set, string value) */
PHP_METHOD(Redis, sContains)
{
REDIS_PROCESS_KW_CMD("SISMEMBER", redis_kv_cmd, redis_1_response);
}
/* }}} */
/* {{{ proto array Redis::sMembers(string set) */
PHP_METHOD(Redis, sMembers)
{
REDIS_PROCESS_KW_CMD("SMEMBERS", redis_key_cmd,
redis_sock_read_multibulk_reply);
}
/* }}} */
/* {{{ proto array Redis::sInter(string key0, ... string keyN) */
PHP_METHOD(Redis, sInter) {
REDIS_PROCESS_CMD(sinter, redis_sock_read_multibulk_reply);
}
/* }}} */
/* {{{ proto array Redis::sInterStore(string dst, string key0,...string keyN) */
PHP_METHOD(Redis, sInterStore) {
REDIS_PROCESS_CMD(sinterstore, redis_long_response);
}
/* }}} */
/* {{{ proto array Redis::sUnion(string key0, ... string keyN) */
PHP_METHOD(Redis, sUnion) {
REDIS_PROCESS_CMD(sunion, redis_sock_read_multibulk_reply);
}
/* }}} */
/* {{{ proto array Redis::sUnionStore(string dst, string key0, ... keyN) */
PHP_METHOD(Redis, sUnionStore) {
REDIS_PROCESS_CMD(sunionstore, redis_long_response);
}
/* }}} */
/* {{{ proto array Redis::sDiff(string key0, ... string keyN) */
PHP_METHOD(Redis, sDiff) {
REDIS_PROCESS_CMD(sdiff, redis_sock_read_multibulk_reply);
}
/* }}} */
/* {{{ proto array Redis::sDiffStore(string dst, string key0, ... keyN) */
PHP_METHOD(Redis, sDiffStore) {
REDIS_PROCESS_CMD(sdiffstore, redis_long_response);
}
/* }}} */
/* {{{ proto array Redis::sort(string key, array options) */
PHP_METHOD(Redis, sort) {
char *cmd;
int cmd_len, have_store;
RedisSock *redis_sock;
// Grab socket, handle command construction
if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL ||
redis_sort_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &have_store,
&cmd, &cmd_len, NULL, NULL)==FAILURE)
{
RETURN_FALSE;
}
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
IF_ATOMIC() {
if (redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,
redis_sock, NULL, NULL) < 0)
{
RETURN_FALSE;
}
}
REDIS_PROCESS_RESPONSE(redis_read_variant_reply);
}
static void
generic_sort_cmd(INTERNAL_FUNCTION_PARAMETERS, int desc, int alpha)
{
zval *object, *zele, *zget = NULL;
RedisSock *redis_sock;
zend_string *zpattern;
char *key = NULL, *pattern = NULL, *store = NULL;
strlen_t keylen, patternlen, storelen;
zend_long offset = -1, count = -1;
int argc = 1; /* SORT key is the simplest SORT command */
smart_string cmd = {0};
/* Parse myriad of sort arguments */
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(),
"Os|s!z!lls", &object, redis_ce, &key,
&keylen, &pattern, &patternlen, &zget,
&offset, &count, &store, &storelen)
== FAILURE)
{
RETURN_FALSE;
}
/* Ensure we're sorting something, and we can get context */
if (keylen == 0 || !(redis_sock = redis_sock_get(object TSRMLS_CC, 0)))
RETURN_FALSE;
/* Start calculating argc depending on input arguments */
if (pattern && patternlen) argc += 2; /* BY pattern */
if (offset >= 0 && count >= 0) argc += 3; /* LIMIT offset count */
if (alpha) argc += 1; /* ALPHA */
if (store) argc += 2; /* STORE destination */
if (desc) argc += 1; /* DESC (ASC is the default) */
/* GET is special. It can be 0 .. N arguments depending what we have */
if (zget) {
if (Z_TYPE_P(zget) == IS_ARRAY)
argc += zend_hash_num_elements(Z_ARRVAL_P(zget));
else if (Z_STRLEN_P(zget) > 0) {
argc += 2; /* GET pattern */
}
}
/* Start constructing final command and append key */
redis_cmd_init_sstr(&cmd, argc, "SORT", 4);
redis_cmd_append_sstr_key(&cmd, key, keylen, redis_sock, NULL);
/* BY pattern */
if (pattern && patternlen) {
redis_cmd_append_sstr(&cmd, "BY", sizeof("BY") - 1);
redis_cmd_append_sstr(&cmd, pattern, patternlen);
}
/* LIMIT offset count */
if (offset >= 0 && count >= 0) {
redis_cmd_append_sstr(&cmd, "LIMIT", sizeof("LIMIT") - 1);
redis_cmd_append_sstr_long(&cmd, offset);
redis_cmd_append_sstr_long(&cmd, count);
}
/* Handle any number of GET pattern arguments we've been passed */
if (zget != NULL) {
if (Z_TYPE_P(zget) == IS_ARRAY) {
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(zget), zele) {
zpattern = zval_get_string(zele);
redis_cmd_append_sstr(&cmd, "GET", sizeof("GET") - 1);
redis_cmd_append_sstr(&cmd, ZSTR_VAL(zpattern), ZSTR_LEN(zpattern));
zend_string_release(zpattern);
} ZEND_HASH_FOREACH_END();
} else {
zpattern = zval_get_string(zget);
redis_cmd_append_sstr(&cmd, "GET", sizeof("GET") - 1);
redis_cmd_append_sstr(&cmd, ZSTR_VAL(zpattern), ZSTR_LEN(zpattern));
zend_string_release(zpattern);
}
}
/* Append optional DESC and ALPHA modifiers */
if (desc) redis_cmd_append_sstr(&cmd, "DESC", sizeof("DESC") - 1);
if (alpha) redis_cmd_append_sstr(&cmd, "ALPHA", sizeof("ALPHA") - 1);
/* Finally append STORE if we've got it */
if (store && storelen) {
redis_cmd_append_sstr(&cmd, "STORE", sizeof("STORE") - 1);
redis_cmd_append_sstr_key(&cmd, store, storelen, redis_sock, NULL);
}
REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len);
IF_ATOMIC() {
if (redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,
redis_sock, NULL, NULL) < 0)
{
RETURN_FALSE;
}
}
REDIS_PROCESS_RESPONSE(redis_read_variant_reply);
}
/* {{{ proto array Redis::sortAsc(string key, string pattern, string get,
* int start, int end, bool getList]) */
PHP_METHOD(Redis, sortAsc)
{
generic_sort_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, 0);
}
/* }}} */
/* {{{ proto array Redis::sortAscAlpha(string key, string pattern, string get,
* int start, int end, bool getList]) */
PHP_METHOD(Redis, sortAscAlpha)
{
generic_sort_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, 1);
}
/* }}} */
/* {{{ proto array Redis::sortDesc(string key, string pattern, string get,
* int start, int end, bool getList]) */
PHP_METHOD(Redis, sortDesc)
{
generic_sort_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1, 0);
}
/* }}} */
/* {{{ proto array Redis::sortDescAlpha(string key, string pattern, string get,
* int start, int end, bool getList]) */
PHP_METHOD(Redis, sortDescAlpha)
{
generic_sort_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1, 1);
}
/* }}} */
/* {{{ proto array Redis::setTimeout(string key, int timeout) */
PHP_METHOD(Redis, setTimeout) {
REDIS_PROCESS_KW_CMD("EXPIRE", redis_key_long_cmd, redis_1_response);
}
/* }}} */
/* {{{ proto bool Redis::pexpire(string key, long ms) */
PHP_METHOD(Redis, pexpire) {
REDIS_PROCESS_KW_CMD("PEXPIRE", redis_key_long_cmd, redis_1_response);
}
/* }}} */
/* {{{ proto array Redis::expireAt(string key, int timestamp) */
PHP_METHOD(Redis, expireAt) {
REDIS_PROCESS_KW_CMD("EXPIREAT", redis_key_long_cmd, redis_1_response);
}
/* }}} */
/* {{{ proto array Redis::pexpireAt(string key, int timestamp) */
PHP_METHOD(Redis, pexpireAt) {
REDIS_PROCESS_KW_CMD("PEXPIREAT", redis_key_long_cmd, redis_1_response);
}
/* }}} */
/* {{{ proto array Redis::lSet(string key, int index, string value) */
PHP_METHOD(Redis, lSet) {
REDIS_PROCESS_KW_CMD("LSET", redis_key_long_val_cmd,
redis_boolean_response);
}
/* }}} */
/* {{{ proto string Redis::save() */
PHP_METHOD(Redis, save)
{
REDIS_PROCESS_KW_CMD("SAVE", redis_empty_cmd, redis_boolean_response);
}
/* }}} */
/* {{{ proto string Redis::bgSave() */
PHP_METHOD(Redis, bgSave)
{
REDIS_PROCESS_KW_CMD("BGSAVE", redis_empty_cmd, redis_boolean_response);
}
/* }}} */
/* {{{ proto integer Redis::lastSave() */
PHP_METHOD(Redis, lastSave)
{
REDIS_PROCESS_KW_CMD("LASTSAVE", redis_empty_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto bool Redis::flushDB() */
PHP_METHOD(Redis, flushDB)
{
REDIS_PROCESS_KW_CMD("FLUSHDB", redis_empty_cmd, redis_boolean_response);
}
/* }}} */
/* {{{ proto bool Redis::flushAll() */
PHP_METHOD(Redis, flushAll)
{
REDIS_PROCESS_KW_CMD("FLUSHALL", redis_empty_cmd, redis_boolean_response);
}
/* }}} */
/* {{{ proto int Redis::dbSize() */
PHP_METHOD(Redis, dbSize)
{
REDIS_PROCESS_KW_CMD("DBSIZE", redis_empty_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto bool Redis::auth(string passwd) */
PHP_METHOD(Redis, auth) {
REDIS_PROCESS_CMD(auth, redis_boolean_response);
}
/* }}} */
/* {{{ proto long Redis::persist(string key) */
PHP_METHOD(Redis, persist) {
REDIS_PROCESS_KW_CMD("PERSIST", redis_key_cmd, redis_1_response);
}
/* }}} */
/* {{{ proto long Redis::ttl(string key) */
PHP_METHOD(Redis, ttl) {
REDIS_PROCESS_KW_CMD("TTL", redis_key_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto long Redis::pttl(string key) */
PHP_METHOD(Redis, pttl) {
REDIS_PROCESS_KW_CMD("PTTL", redis_key_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto array Redis::info() */
PHP_METHOD(Redis, info) {
zval *object;
RedisSock *redis_sock;
char *cmd, *opt = NULL;
strlen_t opt_len;
int cmd_len;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(),
"O|s", &object, redis_ce, &opt, &opt_len)
== FAILURE)
{
RETURN_FALSE;
}
if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
/* Build a standalone INFO command or one with an option */
if (opt != NULL) {
cmd_len = REDIS_SPPRINTF(&cmd, "INFO", "s", opt, opt_len);
} else {
cmd_len = REDIS_SPPRINTF(&cmd, "INFO", "");
}
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
IF_ATOMIC() {
redis_info_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL,
NULL);
}
REDIS_PROCESS_RESPONSE(redis_info_response);
}
/* }}} */
/* {{{ proto bool Redis::select(long dbNumber) */
PHP_METHOD(Redis, select) {
zval *object;
RedisSock *redis_sock;
char *cmd;
int cmd_len;
zend_long dbNumber;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol",
&object, redis_ce, &dbNumber) == FAILURE) {
RETURN_FALSE;
}
if (dbNumber < 0 || (redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
redis_sock->dbNumber = dbNumber;
cmd_len = REDIS_SPPRINTF(&cmd, "SELECT", "d", dbNumber);
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
IF_ATOMIC() {
redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
NULL, NULL);
}
REDIS_PROCESS_RESPONSE(redis_boolean_response);
}
/* }}} */
/* {{{ proto bool Redis::move(string key, long dbindex) */
PHP_METHOD(Redis, move) {
REDIS_PROCESS_KW_CMD("MOVE", redis_key_long_cmd, redis_1_response);
}
/* }}} */
static
void generic_mset(INTERNAL_FUNCTION_PARAMETERS, char *kw, ResultCallback fun)
{
RedisSock *redis_sock;
smart_string cmd = {0};
zval *object, *z_array;
HashTable *htargs;
zend_string *zkey;
zval *zmem;
char buf[64];
size_t keylen;
ulong idx;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa",
&object, redis_ce, &z_array) == FAILURE)
{
RETURN_FALSE;
}
/* Make sure we can get our socket, and we were not passed an empty array */
if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL ||
zend_hash_num_elements(Z_ARRVAL_P(z_array)) == 0)
{
RETURN_FALSE;
}
/* Initialize our command */
htargs = Z_ARRVAL_P(z_array);
redis_cmd_init_sstr(&cmd, zend_hash_num_elements(htargs) * 2, kw, strlen(kw));
ZEND_HASH_FOREACH_KEY_VAL(htargs, idx, zkey, zmem) {
/* Handle string or numeric keys */
if (zkey) {
redis_cmd_append_sstr_key(&cmd, ZSTR_VAL(zkey), ZSTR_LEN(zkey), redis_sock, NULL);
} else {
keylen = snprintf(buf, sizeof(buf), "%ld", (long)idx);
redis_cmd_append_sstr_key(&cmd, buf, (strlen_t)keylen, redis_sock, NULL);
}
/* Append our value */
redis_cmd_append_sstr_zval(&cmd, zmem, redis_sock TSRMLS_CC);
} ZEND_HASH_FOREACH_END();
REDIS_PROCESS_REQUEST(redis_sock, cmd.c, cmd.len);
IF_ATOMIC() {
fun(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
}
REDIS_PROCESS_RESPONSE(fun);
}
/* {{{ proto bool Redis::mset(array (key => value, ...)) */
PHP_METHOD(Redis, mset) {
generic_mset(INTERNAL_FUNCTION_PARAM_PASSTHRU, "MSET", redis_boolean_response);
}
/* }}} */
/* {{{ proto bool Redis::msetnx(array (key => value, ...)) */
PHP_METHOD(Redis, msetnx) {
generic_mset(INTERNAL_FUNCTION_PARAM_PASSTHRU, "MSETNX", redis_1_response);
}
/* }}} */
/* {{{ proto string Redis::rpoplpush(string srckey, string dstkey) */
PHP_METHOD(Redis, rpoplpush)
{
REDIS_PROCESS_KW_CMD("RPOPLPUSH", redis_key_key_cmd, redis_string_response);
}
/* }}} */
/* {{{ proto string Redis::brpoplpush(string src, string dst, int timeout) */
PHP_METHOD(Redis, brpoplpush) {
REDIS_PROCESS_CMD(brpoplpush, redis_string_response);
}
/* }}} */
/* {{{ proto long Redis::zAdd(string key, int score, string value) */
PHP_METHOD(Redis, zAdd) {
REDIS_PROCESS_CMD(zadd, redis_long_response);
}
/* }}} */
/* Handle ZRANGE and ZREVRANGE as they're the same except for keyword */
static void generic_zrange_cmd(INTERNAL_FUNCTION_PARAMETERS, char *kw,
zrange_cb fun)
{
char *cmd;
int cmd_len;
RedisSock *redis_sock;
int withscores=0;
if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
if(fun(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, kw, &cmd,
&cmd_len, &withscores, NULL, NULL)==FAILURE)
{
RETURN_FALSE;
}
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
if(withscores) {
IF_ATOMIC() {
redis_mbulk_reply_zipped_keys_dbl(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
}
REDIS_PROCESS_RESPONSE(redis_mbulk_reply_zipped_keys_dbl);
} else {
IF_ATOMIC() {
if(redis_sock_read_multibulk_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,
redis_sock, NULL, NULL)<0)
{
RETURN_FALSE;
}
}
REDIS_PROCESS_RESPONSE(redis_sock_read_multibulk_reply);
}
}
/* {{{ proto array Redis::zRange(string key,int start,int end,bool scores=0) */
PHP_METHOD(Redis, zRange)
{
generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZRANGE",
redis_zrange_cmd);
}
/* {{{ proto array Redis::zRevRange(string k, long s, long e, bool scores=0) */
PHP_METHOD(Redis, zRevRange) {
generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZREVRANGE",
redis_zrange_cmd);
}
/* }}} */
/* {{{ proto array Redis::zRangeByScore(string k,string s,string e,array opt) */
PHP_METHOD(Redis, zRangeByScore) {
generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZRANGEBYSCORE",
redis_zrangebyscore_cmd);
}
/* }}} */
/* {{{ proto array Redis::zRevRangeByScore(string key, string start, string end,
* array options) */
PHP_METHOD(Redis, zRevRangeByScore) {
generic_zrange_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZREVRANGEBYSCORE",
redis_zrangebyscore_cmd);
}
/* }}} */
/* {{{ proto array Redis::zRangeByLex(string key, string min, string max, [
* offset, limit]) */
PHP_METHOD(Redis, zRangeByLex) {
REDIS_PROCESS_KW_CMD("ZRANGEBYLEX", redis_zrangebylex_cmd,
redis_sock_read_multibulk_reply);
}
/* }}} */
PHP_METHOD(Redis, zRevRangeByLex) {
REDIS_PROCESS_KW_CMD("ZREVRANGEBYLEX", redis_zrangebylex_cmd,
redis_sock_read_multibulk_reply);
}
/* }}} */
/* {{{ proto long Redis::zLexCount(string key, string min, string max) */
PHP_METHOD(Redis, zLexCount) {
REDIS_PROCESS_KW_CMD("ZLEXCOUNT", redis_gen_zlex_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto long Redis::zRemRangeByLex(string key, string min, string max) */
PHP_METHOD(Redis, zRemRangeByLex) {
REDIS_PROCESS_KW_CMD("ZREMRANGEBYLEX", redis_gen_zlex_cmd,
redis_long_response);
}
/* }}} */
/* {{{ proto long Redis::zDelete(string key, string member) */
PHP_METHOD(Redis, zDelete)
{
REDIS_PROCESS_KW_CMD("ZREM", redis_key_varval_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto long Redis::zDeleteRangeByScore(string k, string s, string e) */
PHP_METHOD(Redis, zDeleteRangeByScore)
{
REDIS_PROCESS_KW_CMD("ZREMRANGEBYSCORE", redis_key_str_str_cmd,
redis_long_response);
}
/* }}} */
/* {{{ proto long Redis::zDeleteRangeByRank(string key, long start, long end) */
PHP_METHOD(Redis, zDeleteRangeByRank)
{
REDIS_PROCESS_KW_CMD("ZREMRANGEBYRANK", redis_key_long_long_cmd,
redis_long_response);
}
/* }}} */
/* {{{ proto array Redis::zCount(string key, string start , string end) */
PHP_METHOD(Redis, zCount)
{
REDIS_PROCESS_KW_CMD("ZCOUNT", redis_key_str_str_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto long Redis::zCard(string key) */
PHP_METHOD(Redis, zCard)
{
REDIS_PROCESS_KW_CMD("ZCARD", redis_key_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto double Redis::zScore(string key, mixed member) */
PHP_METHOD(Redis, zScore)
{
REDIS_PROCESS_KW_CMD("ZSCORE", redis_kv_cmd,
redis_bulk_double_response);
}
/* }}} */
/* {{{ proto long Redis::zRank(string key, string member) */
PHP_METHOD(Redis, zRank) {
REDIS_PROCESS_KW_CMD("ZRANK", redis_kv_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto long Redis::zRevRank(string key, string member) */
PHP_METHOD(Redis, zRevRank) {
REDIS_PROCESS_KW_CMD("ZREVRANK", redis_kv_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto double Redis::zIncrBy(string key, double value, mixed member) */
PHP_METHOD(Redis, zIncrBy)
{
REDIS_PROCESS_CMD(zincrby, redis_bulk_double_response);
}
/* }}} */
/* zInter */
PHP_METHOD(Redis, zInter) {
REDIS_PROCESS_KW_CMD("ZINTERSTORE", redis_zinter_cmd, redis_long_response);
}
/* zUnion */
PHP_METHOD(Redis, zUnion) {
REDIS_PROCESS_KW_CMD("ZUNIONSTORE", redis_zinter_cmd, redis_long_response);
}
/* hashes */
/* {{{ proto long Redis::hset(string key, string mem, string val) */
PHP_METHOD(Redis, hSet)
{
REDIS_PROCESS_CMD(hset, redis_long_response);
}
/* }}} */
/* {{{ proto bool Redis::hSetNx(string key, string mem, string val) */
PHP_METHOD(Redis, hSetNx)
{
REDIS_PROCESS_CMD(hsetnx, redis_1_response);
}
/* }}} */
/* {{{ proto string Redis::hget(string key, string mem) */
PHP_METHOD(Redis, hGet)
{
REDIS_PROCESS_KW_CMD("HGET", redis_key_str_cmd, redis_string_response);
}
/* }}} */
/* {{{ proto long Redis::hLen(string key) */
PHP_METHOD(Redis, hLen)
{
REDIS_PROCESS_KW_CMD("HLEN", redis_key_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto long Redis::hDel(string key, string mem1, ... memN) */
PHP_METHOD(Redis, hDel)
{
REDIS_PROCESS_CMD(hdel, redis_long_response);
}
/* }}} */
/* {{{ proto bool Redis::hExists(string key, string mem) */
PHP_METHOD(Redis, hExists)
{
REDIS_PROCESS_KW_CMD("HEXISTS", redis_key_str_cmd, redis_1_response);
}
/* {{{ proto array Redis::hkeys(string key) */
PHP_METHOD(Redis, hKeys)
{
REDIS_PROCESS_KW_CMD("HKEYS", redis_key_cmd, redis_mbulk_reply_raw);
}
/* }}} */
/* {{{ proto array Redis::hvals(string key) */
PHP_METHOD(Redis, hVals)
{
REDIS_PROCESS_KW_CMD("HVALS", redis_key_cmd,
redis_sock_read_multibulk_reply);
}
/* {{{ proto array Redis::hgetall(string key) */
PHP_METHOD(Redis, hGetAll) {
REDIS_PROCESS_KW_CMD("HGETALL", redis_key_cmd, redis_mbulk_reply_zipped_vals);
}
/* }}} */
/* {{{ proto double Redis::hIncrByFloat(string k, string me, double v) */
PHP_METHOD(Redis, hIncrByFloat)
{
REDIS_PROCESS_CMD(hincrbyfloat, redis_bulk_double_response);
}
/* }}} */
/* {{{ proto long Redis::hincrby(string key, string mem, long byval) */
PHP_METHOD(Redis, hIncrBy)
{
REDIS_PROCESS_CMD(hincrby, redis_long_response);
}
/* }}} */
/* {{{ array Redis::hMget(string hash, array keys) */
PHP_METHOD(Redis, hMget) {
REDIS_PROCESS_CMD(hmget, redis_mbulk_reply_assoc);
}
/* }}} */
/* {{{ proto bool Redis::hmset(string key, array keyvals) */
PHP_METHOD(Redis, hMset)
{
REDIS_PROCESS_CMD(hmset, redis_boolean_response);
}
/* }}} */
/* {{{ proto long Redis::hstrlen(string key, string field) */
PHP_METHOD(Redis, hStrLen) {
REDIS_PROCESS_CMD(hstrlen, redis_long_response);
}
/* }}} */
/* flag : get, set {ATOMIC, MULTI, PIPELINE} */
PHP_METHOD(Redis, multi)
{
RedisSock *redis_sock;
char *resp, *cmd;
int resp_len, cmd_len;
zval *object;
zend_long multi_value = MULTI;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(),
"O|l", &object, redis_ce, &multi_value)
== FAILURE)
{
RETURN_FALSE;
}
/* if the flag is activated, send the command, the reply will be "QUEUED"
* or -ERR */
if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
if (multi_value == PIPELINE) {
/* Cannot enter pipeline mode in a MULTI block */
IF_MULTI() {
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Can't activate pipeline in multi mode!");
RETURN_FALSE;
}
/* Enable PIPELINE if we're not already in one */
IF_ATOMIC() {
free_reply_callbacks(redis_sock);
REDIS_ENABLE_MODE(redis_sock, PIPELINE);
}
} else if (multi_value == MULTI) {
/* Don't want to do anything if we're alredy in MULTI mode */
IF_NOT_MULTI() {
cmd_len = REDIS_SPPRINTF(&cmd, "MULTI", "");
IF_PIPELINE() {
PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len);
efree(cmd);
REDIS_SAVE_CALLBACK(NULL, NULL);
REDIS_ENABLE_MODE(redis_sock, MULTI);
} else {
SOCKET_WRITE_COMMAND(redis_sock, cmd, cmd_len)
efree(cmd);
if ((resp = redis_sock_read(redis_sock, &resp_len TSRMLS_CC)) == NULL) {
RETURN_FALSE;
} else if (strncmp(resp, "+OK", 3) != 0) {
efree(resp);
RETURN_FALSE;
}
efree(resp);
REDIS_ENABLE_MODE(redis_sock, MULTI);
}
}
} else {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown mode sent to Redis::multi");
RETURN_FALSE;
}
RETURN_ZVAL(getThis(), 1, 0);
}
/* discard */
PHP_METHOD(Redis, discard)
{
RedisSock *redis_sock;
zval *object;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O",
&object, redis_ce) == FAILURE) {
RETURN_FALSE;
}
if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
redis_sock->mode = ATOMIC;
free_reply_callbacks(redis_sock);
RETURN_BOOL(redis_send_discard(redis_sock TSRMLS_CC) == SUCCESS);
}
/* redis_sock_read_multibulk_multi_reply */
PHP_REDIS_API int redis_sock_read_multibulk_multi_reply(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock)
{
char inbuf[4096];
int numElems;
size_t len;
if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len TSRMLS_CC) < 0) {
return - 1;
}
/* number of responses */
numElems = atoi(inbuf+1);
if(numElems < 0) {
return -1;
}
array_init(return_value);
redis_sock_read_multibulk_multi_reply_loop(INTERNAL_FUNCTION_PARAM_PASSTHRU,
redis_sock, return_value, numElems);
return 0;
}
/* exec */
PHP_METHOD(Redis, exec)
{
RedisSock *redis_sock;
char *cmd;
int cmd_len, ret;
zval *object;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(),
"O", &object, redis_ce) == FAILURE ||
(redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL
) {
RETURN_FALSE;
}
IF_MULTI() {
cmd_len = REDIS_SPPRINTF(&cmd, "EXEC", "");
IF_PIPELINE() {
PIPELINE_ENQUEUE_COMMAND(cmd, cmd_len);
efree(cmd);
REDIS_SAVE_CALLBACK(NULL, NULL);
REDIS_DISABLE_MODE(redis_sock, MULTI);
RETURN_ZVAL(getThis(), 1, 0);
}
SOCKET_WRITE_COMMAND(redis_sock, cmd, cmd_len)
efree(cmd);
ret = redis_sock_read_multibulk_multi_reply(
INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock);
free_reply_callbacks(redis_sock);
REDIS_DISABLE_MODE(redis_sock, MULTI);
redis_sock->watching = 0;
if (ret < 0) {
zval_dtor(return_value);
RETURN_FALSE;
}
}
IF_PIPELINE() {
if (redis_sock->pipeline_cmd == NULL) {
/* Empty array when no command was run. */
array_init(return_value);
} else {
if (redis_sock_write(redis_sock, redis_sock->pipeline_cmd,
redis_sock->pipeline_len TSRMLS_CC) < 0) {
ZVAL_FALSE(return_value);
} else {
array_init(return_value);
redis_sock_read_multibulk_multi_reply_loop(
INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, return_value, 0);
}
efree(redis_sock->pipeline_cmd);
redis_sock->pipeline_cmd = NULL;
redis_sock->pipeline_len = 0;
}
free_reply_callbacks(redis_sock);
REDIS_DISABLE_MODE(redis_sock, PIPELINE);
}
}
PHP_REDIS_API int
redis_response_enqueued(RedisSock *redis_sock TSRMLS_DC)
{
char *resp;
int resp_len, ret = FAILURE;
if ((resp = redis_sock_read(redis_sock, &resp_len TSRMLS_CC)) != NULL) {
if (strncmp(resp, "+QUEUED", 7) == 0) {
ret = SUCCESS;
}
efree(resp);
}
return ret;
}
PHP_REDIS_API int
redis_sock_read_multibulk_multi_reply_loop(INTERNAL_FUNCTION_PARAMETERS,
RedisSock *redis_sock, zval *z_tab,
int numElems)
{
fold_item *fi;
for (fi = redis_sock->head; fi; /* void */) {
if (fi->fun) {
fi->fun(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, z_tab,
fi->ctx TSRMLS_CC);
fi = fi->next;
continue;
}
size_t len;
char inbuf[255];
if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len TSRMLS_CC) < 0) {
} else if (strncmp(inbuf, "+OK", 3) != 0) {
}
while ((fi = fi->next) && fi->fun) {
if (redis_response_enqueued(redis_sock TSRMLS_CC) == SUCCESS) {
} else {
}
}
if (redis_sock_gets(redis_sock, inbuf, sizeof(inbuf) - 1, &len TSRMLS_CC) < 0) {
}
#if (PHP_MAJOR_VERSION < 7)
zval *z_ret;
MAKE_STD_ZVAL(z_ret);
#else
zval zv, *z_ret = &zv;
#endif
array_init(z_ret);
add_next_index_zval(z_tab, z_ret);
int num = atol(inbuf + 1);
if (num > 0 && redis_read_multibulk_recursive(redis_sock, num, z_ret TSRMLS_CC) < 0) {
}
if (fi) fi = fi->next;
}
redis_sock->current = fi;
return 0;
}
PHP_METHOD(Redis, pipeline)
{
RedisSock *redis_sock;
zval *object;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(),
"O", &object, redis_ce) == FAILURE ||
(redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL
) {
RETURN_FALSE;
}
/* User cannot enter MULTI mode if already in a pipeline */
IF_MULTI() {
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Can't activate pipeline in multi mode!");
RETURN_FALSE;
}
/* Enable pipeline mode unless we're already in that mode in which case this
* is just a NO OP */
IF_ATOMIC() {
/* NB : we keep the function fold, to detect the last function.
* We need the response format of the n - 1 command. So, we can delete
* when n > 2, the { 1 .. n - 2} commands */
free_reply_callbacks(redis_sock);
REDIS_ENABLE_MODE(redis_sock, PIPELINE);
}
RETURN_ZVAL(getThis(), 1, 0);
}
/* {{{ proto long Redis::publish(string channel, string msg) */
PHP_METHOD(Redis, publish)
{
REDIS_PROCESS_KW_CMD("PUBLISH", redis_key_str_cmd, redis_long_response);
}
/* }}} */
/* {{{ proto void Redis::psubscribe(Array(pattern1, pattern2, ... patternN)) */
PHP_METHOD(Redis, psubscribe)
{
REDIS_PROCESS_KW_CMD("PSUBSCRIBE", redis_subscribe_cmd,
redis_subscribe_response);
}
/* {{{ proto void Redis::subscribe(Array(channel1, channel2, ... channelN)) */
PHP_METHOD(Redis, subscribe) {
REDIS_PROCESS_KW_CMD("SUBSCRIBE", redis_subscribe_cmd,
redis_subscribe_response);
}
/**
* [p]unsubscribe channel_0 channel_1 ... channel_n
* [p]unsubscribe(array(channel_0, channel_1, ..., channel_n))
* response format :
* array(
* channel_0 => TRUE|FALSE,
* channel_1 => TRUE|FALSE,
* ...
* channel_n => TRUE|FALSE
* );
**/
PHP_REDIS_API void generic_unsubscribe_cmd(INTERNAL_FUNCTION_PARAMETERS,
char *unsub_cmd)
{
zval *object, *array, *data;
HashTable *arr_hash;
RedisSock *redis_sock;
char *cmd = "", *old_cmd = NULL;
int cmd_len, array_count;
int i;
zval z_tab, *z_channel;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oa",
&object, redis_ce, &array) == FAILURE) {
RETURN_FALSE;
}
if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
arr_hash = Z_ARRVAL_P(array);
array_count = zend_hash_num_elements(arr_hash);
if (array_count == 0) {
RETURN_FALSE;
}
ZEND_HASH_FOREACH_VAL(arr_hash, data) {
ZVAL_DEREF(data);
if (Z_TYPE_P(data) == IS_STRING) {
char *old_cmd = NULL;
if(*cmd) {
old_cmd = cmd;
}
spprintf(&cmd, 0, "%s %s", cmd, Z_STRVAL_P(data));
if(old_cmd) {
efree(old_cmd);
}
}
} ZEND_HASH_FOREACH_END();
old_cmd = cmd;
cmd_len = spprintf(&cmd, 0, "%s %s\r\n", unsub_cmd, cmd);
efree(old_cmd);
SOCKET_WRITE_COMMAND(redis_sock, cmd, cmd_len)
efree(cmd);
array_init(return_value);
for (i = 1; i <= array_count; i++) {
redis_sock_read_multibulk_reply_zval(
INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &z_tab);
if (Z_TYPE(z_tab) == IS_ARRAY) {
if ((z_channel = zend_hash_index_find(Z_ARRVAL(z_tab), 1)) == NULL) {
RETURN_FALSE;
}
add_assoc_bool(return_value, Z_STRVAL_P(z_channel), 1);
} else {
//error
zval_dtor(&z_tab);
RETURN_FALSE;
}
zval_dtor(&z_tab);
}
}
PHP_METHOD(Redis, unsubscribe)
{
REDIS_PROCESS_KW_CMD("UNSUBSCRIBE", redis_unsubscribe_cmd,
redis_unsubscribe_response);
}
PHP_METHOD(Redis, punsubscribe)
{
REDIS_PROCESS_KW_CMD("PUNSUBSCRIBE", redis_unsubscribe_cmd,
redis_unsubscribe_response);
}
/* {{{ proto string Redis::bgrewriteaof() */
PHP_METHOD(Redis, bgrewriteaof)
{
REDIS_PROCESS_KW_CMD("BGREWRITEAOF", redis_empty_cmd,
redis_boolean_response);
}
/* }}} */
/* {{{ proto string Redis::slaveof([host, port]) */
PHP_METHOD(Redis, slaveof)
{
zval *object;
RedisSock *redis_sock;
char *cmd = "", *host = NULL;
strlen_t host_len;
zend_long port = 6379;
int cmd_len;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(),
"O|sl", &object, redis_ce, &host,
&host_len, &port) == FAILURE)
{
RETURN_FALSE;
}
if (port < 0 || (redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
if (host && host_len) {
cmd_len = REDIS_SPPRINTF(&cmd, "SLAVEOF", "sd", host, host_len, (int)port);
} else {
cmd_len = REDIS_SPPRINTF(&cmd, "SLAVEOF", "ss", "NO", 2, "ONE", 3);
}
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
IF_ATOMIC() {
redis_boolean_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
NULL, NULL);
}
REDIS_PROCESS_RESPONSE(redis_boolean_response);
}
/* }}} */
/* {{{ proto string Redis::object(key) */
PHP_METHOD(Redis, object)
{
RedisSock *redis_sock;
char *cmd; int cmd_len;
REDIS_REPLY_TYPE rtype;
if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
if(redis_object_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, &rtype,
&cmd, &cmd_len, NULL, NULL)==FAILURE)
{
RETURN_FALSE;
}
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
if(rtype == TYPE_INT) {
IF_ATOMIC() {
redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
NULL, NULL);
}
REDIS_PROCESS_RESPONSE(redis_long_response);
} else {
IF_ATOMIC() {
redis_string_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
NULL, NULL);
}
REDIS_PROCESS_RESPONSE(redis_string_response);
}
}
/* }}} */
/* {{{ proto string Redis::getOption($option) */
PHP_METHOD(Redis, getOption)
{
RedisSock *redis_sock;
if ((redis_sock = redis_sock_get_instance(getThis() TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
redis_getoption_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL);
}
/* }}} */
/* {{{ proto string Redis::setOption(string $option, mixed $value) */
PHP_METHOD(Redis, setOption)
{
RedisSock *redis_sock;
if ((redis_sock = redis_sock_get_instance(getThis() TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
redis_setoption_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL);
}
/* }}} */
/* {{{ proto boolean Redis::config(string op, string key [, mixed value]) */
PHP_METHOD(Redis, config)
{
zval *object;
RedisSock *redis_sock;
char *key = NULL, *val = NULL, *cmd, *op = NULL;
strlen_t key_len, val_len, op_len;
enum {CFG_GET, CFG_SET} mode;
int cmd_len;
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(),
"Oss|s", &object, redis_ce, &op, &op_len,
&key, &key_len, &val, &val_len) == FAILURE)
{
RETURN_FALSE;
}
/* op must be GET or SET */
if(strncasecmp(op, "GET", 3) == 0) {
mode = CFG_GET;
} else if(strncasecmp(op, "SET", 3) == 0) {
mode = CFG_SET;
} else {
RETURN_FALSE;
}
if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
if (mode == CFG_GET && val == NULL) {
cmd_len = REDIS_SPPRINTF(&cmd, "CONFIG", "ss", op, op_len, key, key_len);
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len)
IF_ATOMIC() {
redis_mbulk_reply_zipped_raw(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
}
REDIS_PROCESS_RESPONSE(redis_mbulk_reply_zipped_raw);
} else if(mode == CFG_SET && val != NULL) {
cmd_len = REDIS_SPPRINTF(&cmd, "CONFIG", "sss", op, op_len, key, key_len, val, val_len);
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len)
IF_ATOMIC() {
redis_boolean_response(
INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL, NULL);
}
REDIS_PROCESS_RESPONSE(redis_boolean_response);
} else {
RETURN_FALSE;
}
}
/* }}} */
/* {{{ proto boolean Redis::slowlog(string arg, [int option]) */
PHP_METHOD(Redis, slowlog) {
zval *object;
RedisSock *redis_sock;
char *arg, *cmd;
int cmd_len;
strlen_t arg_len;
zend_long option = 0;
enum {SLOWLOG_GET, SLOWLOG_LEN, SLOWLOG_RESET} mode;
// Make sure we can get parameters
if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(),
"Os|l", &object, redis_ce, &arg, &arg_len,
&option) == FAILURE)
{
RETURN_FALSE;
}
/* Figure out what kind of slowlog command we're executing */
if(!strncasecmp(arg, "GET", 3)) {
mode = SLOWLOG_GET;
} else if(!strncasecmp(arg, "LEN", 3)) {
mode = SLOWLOG_LEN;
} else if(!strncasecmp(arg, "RESET", 5)) {
mode = SLOWLOG_RESET;
} else {
/* This command is not valid */
RETURN_FALSE;
}
/* Make sure we can grab our redis socket */
if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
// Create our command. For everything except SLOWLOG GET (with an arg) it's
// just two parts
if (mode == SLOWLOG_GET && ZEND_NUM_ARGS() == 2) {
cmd_len = REDIS_SPPRINTF(&cmd, "SLOWLOG", "sl", arg, arg_len, option);
} else {
cmd_len = REDIS_SPPRINTF(&cmd, "SLOWLOG", "s", arg, arg_len);
}
/* Kick off our command */
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
IF_ATOMIC() {
if(redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,
redis_sock, NULL, NULL) < 0)
{
RETURN_FALSE;
}
}
REDIS_PROCESS_RESPONSE(redis_read_variant_reply);
}
/* {{{ proto Redis::wait(int num_slaves, int ms) }}} */
PHP_METHOD(Redis, wait) {
zval *object;
RedisSock *redis_sock;
zend_long num_slaves, timeout;
char *cmd;
int cmd_len;
/* Make sure arguments are valid */
if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oll",
&object, redis_ce, &num_slaves, &timeout)
==FAILURE)
{
RETURN_FALSE;
}
/* Don't even send this to Redis if our args are negative */
if(num_slaves < 0 || timeout < 0) {
RETURN_FALSE;
}
/* Grab our socket */
if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
// Construct the command
cmd_len = REDIS_SPPRINTF(&cmd, "WAIT", "ll", num_slaves, timeout);
/* Kick it off */
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
IF_ATOMIC() {
redis_long_response(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock, NULL,
NULL);
}
REDIS_PROCESS_RESPONSE(redis_long_response);
}
/* Construct a PUBSUB command */
PHP_REDIS_API int
redis_build_pubsub_cmd(RedisSock *redis_sock, char **ret, PUBSUB_TYPE type,
zval *arg TSRMLS_DC)
{
HashTable *ht_chan;
zval *z_ele;
smart_string cmd = {0};
if (type == PUBSUB_CHANNELS) {
if (arg) {
/* With a pattern */
return REDIS_SPPRINTF(ret, "PUBSUB", "sk", "CHANNELS", sizeof("CHANNELS") - 1,
Z_STRVAL_P(arg), Z_STRLEN_P(arg));
} else {
/* No pattern */
return REDIS_SPPRINTF(ret, "PUBSUB", "s", "CHANNELS", sizeof("CHANNELS") - 1);
}
} else if (type == PUBSUB_NUMSUB) {
ht_chan = Z_ARRVAL_P(arg);
// Add PUBSUB and NUMSUB bits
redis_cmd_init_sstr(&cmd, zend_hash_num_elements(ht_chan)+1, "PUBSUB", sizeof("PUBSUB")-1);
redis_cmd_append_sstr(&cmd, "NUMSUB", sizeof("NUMSUB")-1);
/* Iterate our elements */
ZEND_HASH_FOREACH_VAL(ht_chan, z_ele) {
zend_string *zstr = zval_get_string(z_ele);
redis_cmd_append_sstr_key(&cmd, ZSTR_VAL(zstr), ZSTR_LEN(zstr), redis_sock, NULL);
zend_string_release(zstr);
} ZEND_HASH_FOREACH_END();
/* Set return */
*ret = cmd.c;
return cmd.len;
} else if (type == PUBSUB_NUMPAT) {
return REDIS_SPPRINTF(ret, "PUBSUB", "s", "NUMPAT", sizeof("NUMPAT") - 1);
}
/* Shouldn't ever happen */
return -1;
}
/*
* {{{ proto Redis::pubsub("channels", pattern);
* proto Redis::pubsub("numsub", Array channels);
* proto Redis::pubsub("numpat"); }}}
*/
PHP_METHOD(Redis, pubsub) {
zval *object;
RedisSock *redis_sock;
char *keyword, *cmd;
int cmd_len;
strlen_t kw_len;
PUBSUB_TYPE type;
zval *arg=NULL;
// Parse arguments
if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(),
"Os|z", &object, redis_ce, &keyword,
&kw_len, &arg)==FAILURE)
{
RETURN_FALSE;
}
/* Validate our sub command keyword, and that we've got proper arguments */
if(!strncasecmp(keyword, "channels", sizeof("channels"))) {
/* One (optional) string argument */
if(arg && Z_TYPE_P(arg) != IS_STRING) {
RETURN_FALSE;
}
type = PUBSUB_CHANNELS;
} else if(!strncasecmp(keyword, "numsub", sizeof("numsub"))) {
/* One array argument */
if(ZEND_NUM_ARGS() < 2 || Z_TYPE_P(arg) != IS_ARRAY ||
zend_hash_num_elements(Z_ARRVAL_P(arg))==0)
{
RETURN_FALSE;
}
type = PUBSUB_NUMSUB;
} else if(!strncasecmp(keyword, "numpat", sizeof("numpat"))) {
type = PUBSUB_NUMPAT;
} else {
/* Invalid keyword */
RETURN_FALSE;
}
/* Grab our socket context object */
if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
/* Construct our "PUBSUB" command */
cmd_len = redis_build_pubsub_cmd(redis_sock, &cmd, type, arg TSRMLS_CC);
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
if(type == PUBSUB_NUMSUB) {
IF_ATOMIC() {
if(redis_mbulk_reply_zipped_keys_int(INTERNAL_FUNCTION_PARAM_PASSTHRU,
redis_sock, NULL, NULL)<0)
{
RETURN_FALSE;
}
}
REDIS_PROCESS_RESPONSE(redis_mbulk_reply_zipped_keys_int);
} else {
IF_ATOMIC() {
if(redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,
redis_sock, NULL, NULL)<0)
{
RETURN_FALSE;
}
}
REDIS_PROCESS_RESPONSE(redis_read_variant_reply);
}
}
/* {{{ proto variant Redis::eval(string script, [array keys, long num_keys]) */
PHP_METHOD(Redis, eval)
{
REDIS_PROCESS_KW_CMD("EVAL", redis_eval_cmd, redis_read_variant_reply);
}
/* {{{ proto variant Redis::evalsha(string sha1, [array keys, long num_keys]) */
PHP_METHOD(Redis, evalsha) {
REDIS_PROCESS_KW_CMD("EVALSHA", redis_eval_cmd, redis_read_variant_reply);
}
PHP_REDIS_API int
redis_build_script_exists_cmd(char **ret, zval *argv, int argc) {
smart_string cmd = {0};
zend_string *zstr;
int i;
// Start building our command
REDIS_CMD_INIT_SSTR_STATIC(&cmd, 1 + argc, "SCRIPT");
redis_cmd_append_sstr(&cmd, "EXISTS", 6);
for (i = 0; i < argc; i++) {
zstr = zval_get_string(&argv[i]);
redis_cmd_append_sstr(&cmd, ZSTR_VAL(zstr), ZSTR_LEN(zstr));
zend_string_release(zstr);
}
/* Success */
*ret = cmd.c;
return cmd.len;
}
/* {{{ proto status Redis::script('flush')
* {{{ proto status Redis::script('kill')
* {{{ proto string Redis::script('load', lua_script)
* {{{ proto int Reids::script('exists', script_sha1 [, script_sha2, ...])
*/
PHP_METHOD(Redis, script) {
zval *z_args;
RedisSock *redis_sock;
int cmd_len, argc;
char *cmd;
/* Attempt to grab our socket */
if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
/* Grab the number of arguments */
argc = ZEND_NUM_ARGS();
/* Allocate an array big enough to store our arguments */
z_args = emalloc(argc * sizeof(zval));
/* Make sure we can grab our arguments, we have a string directive */
if (zend_get_parameters_array(ht, argc, z_args) == FAILURE ||
(argc < 1 || Z_TYPE(z_args[0]) != IS_STRING))
{
efree(z_args);
RETURN_FALSE;
}
// Branch based on the directive
if(!strcasecmp(Z_STRVAL(z_args[0]), "flush") ||
!strcasecmp(Z_STRVAL(z_args[0]), "kill"))
{
// Simple SCRIPT FLUSH, or SCRIPT_KILL command
cmd_len = REDIS_SPPRINTF(&cmd, "SCRIPT", "s", Z_STRVAL(z_args[0]), Z_STRLEN(z_args[0]));
} else if(!strcasecmp(Z_STRVAL(z_args[0]), "load")) {
// Make sure we have a second argument, and it's not empty. If it is
// empty, we can just return an empty array (which is what Redis does)
if(argc < 2 || Z_TYPE(z_args[1]) != IS_STRING ||
Z_STRLEN(z_args[1]) < 1)
{
// Free our args
efree(z_args);
RETURN_FALSE;
}
// Format our SCRIPT LOAD command
cmd_len = REDIS_SPPRINTF(&cmd, "SCRIPT", "ss", "LOAD", 4, Z_STRVAL(z_args[1]),
Z_STRLEN(z_args[1]));
} else if(!strcasecmp(Z_STRVAL(z_args[0]), "exists")) {
/* Construct our SCRIPT EXISTS command */
cmd_len = redis_build_script_exists_cmd(&cmd, &(z_args[1]), argc-1);
} else {
/* Unknown directive */
efree(z_args);
RETURN_FALSE;
}
/* Free our alocated arguments */
efree(z_args);
// Kick off our request
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
IF_ATOMIC() {
if(redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,
redis_sock, NULL, NULL) < 0)
{
RETURN_FALSE;
}
}
REDIS_PROCESS_RESPONSE(redis_read_variant_reply);
}
/* {{{ proto DUMP key */
PHP_METHOD(Redis, dump) {
REDIS_PROCESS_KW_CMD("DUMP", redis_key_cmd, redis_string_response);
}
/* }}} */
/* {{{ proto Redis::restore(ttl, key, value) */
PHP_METHOD(Redis, restore) {
REDIS_PROCESS_KW_CMD("RESTORE", redis_key_long_val_cmd,
redis_boolean_response);
}
/* }}} */
/* {{{ proto Redis::debug(string key) */
PHP_METHOD(Redis, debug) {
REDIS_PROCESS_KW_CMD("DEBUG", redis_key_cmd, redis_string_response);
}
/* }}} */
/* {{{ proto Redis::migrate(host port key dest-db timeout [bool copy,
* bool replace]) */
PHP_METHOD(Redis, migrate) {
REDIS_PROCESS_CMD(migrate, redis_boolean_response);
}
/* {{{ proto Redis::_prefix(key) */
PHP_METHOD(Redis, _prefix) {
RedisSock *redis_sock;
if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
redis_prefix_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock);
}
/* {{{ proto Redis::_serialize(value) */
PHP_METHOD(Redis, _serialize) {
RedisSock *redis_sock;
// Grab socket
if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
redis_serialize_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock);
}
/* {{{ proto Redis::_unserialize(value) */
PHP_METHOD(Redis, _unserialize) {
RedisSock *redis_sock;
// Grab socket
if ((redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
redis_unserialize_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU, redis_sock,
redis_exception_ce);
}
/* {{{ proto Redis::getLastError() */
PHP_METHOD(Redis, getLastError) {
zval *object;
RedisSock *redis_sock;
// Grab our object
if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O",
&object, redis_ce) == FAILURE)
{
RETURN_FALSE;
}
// Grab socket
if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
/* Return our last error or NULL if we don't have one */
if (redis_sock->err) {
RETURN_STRINGL(ZSTR_VAL(redis_sock->err), ZSTR_LEN(redis_sock->err));
}
RETURN_NULL();
}
/* {{{ proto Redis::clearLastError() */
PHP_METHOD(Redis, clearLastError) {
zval *object;
RedisSock *redis_sock;
// Grab our object
if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O",
&object, redis_ce) == FAILURE)
{
RETURN_FALSE;
}
// Grab socket
if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
// Clear error message
if (redis_sock->err) {
zend_string_release(redis_sock->err);
redis_sock->err = NULL;
}
RETURN_TRUE;
}
/*
* {{{ proto long Redis::getMode()
*/
PHP_METHOD(Redis, getMode) {
zval *object;
RedisSock *redis_sock;
/* Grab our object */
if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &object, redis_ce) == FAILURE) {
RETURN_FALSE;
}
/* Grab socket */
if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
IF_PIPELINE() {
RETVAL_LONG(PIPELINE);
} else IF_MULTI() {
RETVAL_LONG(MULTI);
} else {
RETVAL_LONG(ATOMIC);
}
}
/* {{{ proto Redis::time() */
PHP_METHOD(Redis, time) {
REDIS_PROCESS_KW_CMD("TIME", redis_empty_cmd, redis_mbulk_reply_raw);
}
/* {{{ proto array Redis::role() */
PHP_METHOD(Redis, role) {
REDIS_PROCESS_KW_CMD("ROLE", redis_empty_cmd, redis_read_variant_reply);
}
/*
* Introspection stuff
*/
/* {{{ proto Redis::IsConnected */
PHP_METHOD(Redis, isConnected) {
RedisSock *redis_sock;
if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) {
RETURN_TRUE;
} else {
RETURN_FALSE;
}
}
/* {{{ proto Redis::getHost() */
PHP_METHOD(Redis, getHost) {
RedisSock *redis_sock;
if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) {
RETURN_STRINGL(ZSTR_VAL(redis_sock->host), ZSTR_LEN(redis_sock->host));
} else {
RETURN_FALSE;
}
}
/* {{{ proto Redis::getPort() */
PHP_METHOD(Redis, getPort) {
RedisSock *redis_sock;
if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) {
/* Return our port */
RETURN_LONG(redis_sock->port);
} else {
RETURN_FALSE;
}
}
/* {{{ proto Redis::getDBNum */
PHP_METHOD(Redis, getDBNum) {
RedisSock *redis_sock;
if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) {
/* Return our db number */
RETURN_LONG(redis_sock->dbNumber);
} else {
RETURN_FALSE;
}
}
/* {{{ proto Redis::getTimeout */
PHP_METHOD(Redis, getTimeout) {
RedisSock *redis_sock;
if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) {
RETURN_DOUBLE(redis_sock->timeout);
} else {
RETURN_FALSE;
}
}
/* {{{ proto Redis::getReadTimeout */
PHP_METHOD(Redis, getReadTimeout) {
RedisSock *redis_sock;
if((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU))) {
RETURN_DOUBLE(redis_sock->read_timeout);
} else {
RETURN_FALSE;
}
}
/* {{{ proto Redis::getPersistentID */
PHP_METHOD(Redis, getPersistentID) {
RedisSock *redis_sock;
if ((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU)) == NULL) {
RETURN_FALSE;
} else if (redis_sock->persistent_id == NULL) {
RETURN_NULL();
}
RETURN_STRINGL(ZSTR_VAL(redis_sock->persistent_id), ZSTR_LEN(redis_sock->persistent_id));
}
/* {{{ proto Redis::getAuth */
PHP_METHOD(Redis, getAuth) {
RedisSock *redis_sock;
if ((redis_sock = redis_sock_get_connected(INTERNAL_FUNCTION_PARAM_PASSTHRU)) == NULL) {
RETURN_FALSE;
} else if (redis_sock->auth == NULL) {
RETURN_NULL();
}
RETURN_STRINGL(ZSTR_VAL(redis_sock->auth), ZSTR_LEN(redis_sock->auth));
}
/*
* $redis->client('list');
* $redis->client('kill', );
* $redis->client('setname', );
* $redis->client('getname');
*/
PHP_METHOD(Redis, client) {
zval *object;
RedisSock *redis_sock;
char *cmd, *opt=NULL, *arg=NULL;
strlen_t opt_len, arg_len;
int cmd_len;
// Parse our method parameters
if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(),
"Os|s", &object, redis_ce, &opt, &opt_len,
&arg, &arg_len) == FAILURE)
{
RETURN_FALSE;
}
/* Grab our socket */
if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
/* Build our CLIENT command */
if (ZEND_NUM_ARGS() == 2) {
cmd_len = REDIS_SPPRINTF(&cmd, "CLIENT", "ss", opt, opt_len, arg, arg_len);
} else {
cmd_len = REDIS_SPPRINTF(&cmd, "CLIENT", "s", opt, opt_len);
}
/* Execute our queue command */
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
/* We handle CLIENT LIST with a custom response function */
if(!strncasecmp(opt, "list", 4)) {
IF_ATOMIC() {
redis_client_list_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,redis_sock,
NULL);
}
REDIS_PROCESS_RESPONSE(redis_client_list_reply);
} else {
IF_ATOMIC() {
redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,
redis_sock,NULL,NULL);
}
REDIS_PROCESS_RESPONSE(redis_read_variant_reply);
}
}
/* {{{ proto mixed Redis::rawcommand(string $command, [ $arg1 ... $argN]) */
PHP_METHOD(Redis, rawcommand) {
int argc = ZEND_NUM_ARGS(), cmd_len;
char *cmd = NULL;
RedisSock *redis_sock;
zval *z_args;
/* Sanity check on arguments */
if (argc < 1) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"Must pass at least one command keyword");
RETURN_FALSE;
}
z_args = emalloc(argc * sizeof(zval));
if (zend_get_parameters_array(ht, argc, z_args) == FAILURE) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"Internal PHP error parsing arguments");
efree(z_args);
RETURN_FALSE;
} else if (redis_build_raw_cmd(z_args, argc, &cmd, &cmd_len TSRMLS_CC) < 0 ||
(redis_sock = redis_sock_get(getThis() TSRMLS_CC, 0)) == NULL
) {
if (cmd) efree(cmd);
efree(z_args);
RETURN_FALSE;
}
/* Clean up command array */
efree(z_args);
/* Execute our command */
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
IF_ATOMIC() {
redis_read_variant_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,redis_sock,NULL,NULL);
}
REDIS_PROCESS_RESPONSE(redis_read_variant_reply);
}
/* }}} */
/* {{{ proto array Redis::command()
* proto array Redis::command('info', string cmd)
* proto array Redis::command('getkeys', array cmd_args) */
PHP_METHOD(Redis, command) {
REDIS_PROCESS_CMD(command, redis_read_variant_reply);
}
/* }}} */
/* Helper to format any combination of SCAN arguments */
PHP_REDIS_API int
redis_build_scan_cmd(char **cmd, REDIS_SCAN_TYPE type, char *key, int key_len,
int iter, char *pattern, int pattern_len, int count)
{
smart_string cmdstr = {0};
char *keyword;
int argc;
/* Count our arguments +1 for key if it's got one, and + 2 for pattern */
/* or count given that they each carry keywords with them. */
argc = 1 + (key_len > 0) + (pattern_len > 0 ? 2 : 0) + (count > 0 ? 2 : 0);
/* Turn our type into a keyword */
switch(type) {
case TYPE_SCAN:
keyword = "SCAN";
break;
case TYPE_SSCAN:
keyword = "SSCAN";
break;
case TYPE_HSCAN:
keyword = "HSCAN";
break;
case TYPE_ZSCAN:
default:
keyword = "ZSCAN";
break;
}
/* Start the command */
redis_cmd_init_sstr(&cmdstr, argc, keyword, strlen(keyword));
if (key_len) redis_cmd_append_sstr(&cmdstr, key, key_len);
redis_cmd_append_sstr_int(&cmdstr, iter);
/* Append COUNT if we've got it */
if(count) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "COUNT");
redis_cmd_append_sstr_int(&cmdstr, count);
}
/* Append MATCH if we've got it */
if(pattern_len) {
REDIS_CMD_APPEND_SSTR_STATIC(&cmdstr, "MATCH");
redis_cmd_append_sstr(&cmdstr, pattern, pattern_len);
}
/* Return our command length */
*cmd = cmdstr.c;
return cmdstr.len;
}
/* {{{ proto redis::scan(&$iterator, [pattern, [count]]) */
PHP_REDIS_API void
generic_scan_cmd(INTERNAL_FUNCTION_PARAMETERS, REDIS_SCAN_TYPE type) {
zval *object, *z_iter;
RedisSock *redis_sock;
HashTable *hash;
char *pattern=NULL, *cmd, *key=NULL;
int cmd_len, num_elements, key_free=0;
strlen_t key_len = 0, pattern_len = 0;
zend_long count=0, iter;
/* Different prototype depending on if this is a key based scan */
if(type != TYPE_SCAN) {
// Requires a key
if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(),
"Osz/|s!l", &object, redis_ce, &key,
&key_len, &z_iter, &pattern,
&pattern_len, &count)==FAILURE)
{
RETURN_FALSE;
}
} else {
// Doesn't require a key
if(zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(),
"Oz/|s!l", &object, redis_ce, &z_iter,
&pattern, &pattern_len, &count)
== FAILURE)
{
RETURN_FALSE;
}
}
/* Grab our socket */
if ((redis_sock = redis_sock_get(object TSRMLS_CC, 0)) == NULL) {
RETURN_FALSE;
}
/* Calling this in a pipeline makes no sense */
IF_NOT_ATOMIC() {
php_error_docref(NULL TSRMLS_CC, E_ERROR,
"Can't call SCAN commands in multi or pipeline mode!");
RETURN_FALSE;
}
// The iterator should be passed in as NULL for the first iteration, but we
// can treat any NON LONG value as NULL for these purposes as we've
// seperated the variable anyway.
if(Z_TYPE_P(z_iter) != IS_LONG || Z_LVAL_P(z_iter)<0) {
/* Convert to long */
convert_to_long(z_iter);
iter = 0;
} else if(Z_LVAL_P(z_iter)!=0) {
/* Update our iterator value for the next passthru */
iter = Z_LVAL_P(z_iter);
} else {
/* We're done, back to iterator zero */
RETURN_FALSE;
}
/* Prefix our key if we've got one and we have a prefix set */
if(key_len) {
key_free = redis_key_prefix(redis_sock, &key, &key_len);
}
/**
* Redis can return to us empty keys, especially in the case where there
* are a large number of keys to scan, and we're matching against a
* pattern. phpredis can be set up to abstract this from the user, by
* setting OPT_SCAN to REDIS_SCAN_RETRY. Otherwise we will return empty
* keys and the user will need to make subsequent calls with an updated
* iterator.
*/
do {
/* Free our previous reply if we're back in the loop. We know we are
* if our return_value is an array */
if (Z_TYPE_P(return_value) == IS_ARRAY) {
zval_dtor(return_value);
ZVAL_NULL(return_value);
}
// Format our SCAN command
cmd_len = redis_build_scan_cmd(&cmd, type, key, key_len, (int)iter,
pattern, pattern_len, count);
/* Execute our command getting our new iterator value */
REDIS_PROCESS_REQUEST(redis_sock, cmd, cmd_len);
if(redis_sock_read_scan_reply(INTERNAL_FUNCTION_PARAM_PASSTHRU,
redis_sock,type,&iter)<0)
{
if(key_free) efree(key);
RETURN_FALSE;
}
/* Get the number of elements */
hash = Z_ARRVAL_P(return_value);
num_elements = zend_hash_num_elements(hash);
} while(redis_sock->scan == REDIS_SCAN_RETRY && iter != 0 &&
num_elements == 0);
/* Free our key if it was prefixed */
if(key_free) efree(key);
/* Update our iterator reference */
Z_LVAL_P(z_iter) = iter;
}
PHP_METHOD(Redis, scan) {
generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_SCAN);
}
PHP_METHOD(Redis, hscan) {
generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_HSCAN);
}
PHP_METHOD(Redis, sscan) {
generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_SSCAN);
}
PHP_METHOD(Redis, zscan) {
generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, TYPE_ZSCAN);
}
/*
* HyperLogLog based commands
*/
/* {{{ proto Redis::pfAdd(string key, array elements) }}} */
PHP_METHOD(Redis, pfadd) {
REDIS_PROCESS_CMD(pfadd, redis_long_response);
}
/* {{{ proto Redis::pfCount(string key) }}}*/
PHP_METHOD(Redis, pfcount) {
REDIS_PROCESS_CMD(pfcount, redis_long_response);
}
/* {{{ proto Redis::pfMerge(string dstkey, array keys) }}}*/
PHP_METHOD(Redis, pfmerge) {
REDIS_PROCESS_CMD(pfmerge, redis_boolean_response);
}
/*
* Geo commands
*/
PHP_METHOD(Redis, geoadd) {
REDIS_PROCESS_KW_CMD("GEOADD", redis_key_varval_cmd, redis_long_response);
}
PHP_METHOD(Redis, geohash) {
REDIS_PROCESS_KW_CMD("GEOHASH", redis_key_varval_cmd, redis_mbulk_reply_raw);
}
PHP_METHOD(Redis, geopos) {
REDIS_PROCESS_KW_CMD("GEOPOS", redis_key_varval_cmd, redis_read_variant_reply);
}
PHP_METHOD(Redis, geodist) {
REDIS_PROCESS_CMD(geodist, redis_bulk_double_response);
}
PHP_METHOD(Redis, georadius) {
REDIS_PROCESS_CMD(georadius, redis_read_variant_reply);
}
PHP_METHOD(Redis, georadiusbymember) {
REDIS_PROCESS_CMD(georadiusbymember, redis_read_variant_reply);
}
/* vim: set tabstop=4 softtabstop=4 expandtab shiftwidth=4: */
redis-3.1.6/crc16.h 0000644 0001750 0000012 00000010603 13223116600 014467 0 ustar pyatsukhnenko wheel /*
* Copyright 2001-2010 Georges Menie (www.menie.org)
* Copyright 2010 Salvatore Sanfilippo (adapted to Redis coding style)
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the University of California, Berkeley nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* CRC16 implementation according to CCITT standards.
*
* Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the
* following parameters:
*
* Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN"
* Width : 16 bit
* Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1)
* Initialization : 0000
* Reflect Input byte : False
* Reflect Output CRC : False
* Xor constant to output CRC : 0000
* Output for "123456789" : 31C3
*/
#include
static const uint16_t crc16tab[256]= {
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
};
static inline uint16_t crc16(const char *buf, int len) {
int counter;
uint16_t crc = 0;
for (counter = 0; counter < len; counter++)
crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF];
return crc;
}
redis-3.1.6/redis_session.c 0000644 0001750 0000012 00000057466 13223116600 016437 0 ustar pyatsukhnenko wheel /* -*- Mode: C; tab-width: 4 -*- */
/*
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2009 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Original author: Alfonso Jimenez |
| Maintainer: Nicolas Favre-Felix |
| Maintainer: Nasreddine Bouafif |
| Maintainer: Michael Grunder |
+----------------------------------------------------------------------+
*/
#include "common.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef PHP_SESSION
#include "ext/standard/info.h"
#include "php_redis.h"
#include "redis_session.h"
#include
#include "library.h"
#include "cluster_library.h"
#include "php.h"
#include "php_ini.h"
#include "php_variables.h"
#include "SAPI.h"
#include "ext/standard/url.h"
ps_module ps_mod_redis = {
PS_MOD(redis)
};
ps_module ps_mod_redis_cluster = {
PS_MOD(rediscluster)
};
typedef struct redis_pool_member_ {
RedisSock *redis_sock;
int weight;
int database;
char *prefix;
size_t prefix_len;
char *auth;
size_t auth_len;
struct redis_pool_member_ *next;
} redis_pool_member;
typedef struct {
int totalWeight;
int count;
redis_pool_member *head;
} redis_pool;
PHP_REDIS_API redis_pool*
redis_pool_new(TSRMLS_D) {
return ecalloc(1, sizeof(redis_pool));
}
PHP_REDIS_API void
redis_pool_add(redis_pool *pool, RedisSock *redis_sock, int weight,
int database, char *prefix, char *auth TSRMLS_DC) {
redis_pool_member *rpm = ecalloc(1, sizeof(redis_pool_member));
rpm->redis_sock = redis_sock;
rpm->weight = weight;
rpm->database = database;
rpm->prefix = prefix;
rpm->prefix_len = (prefix?strlen(prefix):0);
rpm->auth = auth;
rpm->auth_len = (auth?strlen(auth):0);
rpm->next = pool->head;
pool->head = rpm;
pool->totalWeight += weight;
}
PHP_REDIS_API void
redis_pool_free(redis_pool *pool TSRMLS_DC) {
redis_pool_member *rpm, *next;
rpm = pool->head;
while(rpm) {
next = rpm->next;
redis_sock_disconnect(rpm->redis_sock TSRMLS_CC);
redis_free_socket(rpm->redis_sock);
if(rpm->prefix) efree(rpm->prefix);
if(rpm->auth) efree(rpm->auth);
efree(rpm);
rpm = next;
}
efree(pool);
}
static void
redis_pool_member_auth(redis_pool_member *rpm TSRMLS_DC) {
RedisSock *redis_sock = rpm->redis_sock;
char *response, *cmd;
int response_len, cmd_len;
/* Short circuit if we don't have a password */
if(!rpm->auth || !rpm->auth_len) {
return;
}
cmd_len = REDIS_SPPRINTF(&cmd, "AUTH", "s", rpm->auth, rpm->auth_len);
if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) >= 0) {
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC))) {
efree(response);
}
}
efree(cmd);
}
static void
redis_pool_member_select(redis_pool_member *rpm TSRMLS_DC) {
RedisSock *redis_sock = rpm->redis_sock;
char *response, *cmd;
int response_len, cmd_len;
cmd_len = REDIS_SPPRINTF(&cmd, "SELECT", "d", rpm->database);
if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) >= 0) {
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC))) {
efree(response);
}
}
efree(cmd);
}
PHP_REDIS_API redis_pool_member *
redis_pool_get_sock(redis_pool *pool, const char *key TSRMLS_DC) {
unsigned int pos, i;
memcpy(&pos, key, sizeof(pos));
pos %= pool->totalWeight;
redis_pool_member *rpm = pool->head;
for(i = 0; i < pool->totalWeight;) {
if(pos >= i && pos < i + rpm->weight) {
int needs_auth = 0;
if(rpm->auth && rpm->auth_len && rpm->redis_sock->status != REDIS_SOCK_STATUS_CONNECTED) {
needs_auth = 1;
}
redis_sock_server_open(rpm->redis_sock TSRMLS_CC);
if(needs_auth) {
redis_pool_member_auth(rpm TSRMLS_CC);
}
if(rpm->database >= 0) { /* default is -1 which leaves the choice to redis. */
redis_pool_member_select(rpm TSRMLS_CC);
}
return rpm;
}
i += rpm->weight;
rpm = rpm->next;
}
return NULL;
}
/* {{{ PS_OPEN_FUNC
*/
PS_OPEN_FUNC(redis)
{
php_url *url;
zval params, *param;
int i, j, path_len;
redis_pool *pool = redis_pool_new(TSRMLS_C);
for (i=0,j=0,path_len=strlen(save_path); iquery != NULL) {
array_init(¶ms);
#if (PHP_VERSION_ID < 70300)
sapi_module.treat_data(PARSE_STRING, estrdup(url->query), ¶ms TSRMLS_CC);
#else
sapi_module.treat_data(PARSE_STRING, estrndup(ZSTR_VAL(url->query), ZSTR_LEN(url->query)), ¶ms TSRMLS_CC);
#endif
if ((param = zend_hash_str_find(Z_ARRVAL(params), "weight", sizeof("weight") - 1)) != NULL) {
weight = zval_get_long(param);
}
if ((param = zend_hash_str_find(Z_ARRVAL(params), "timeout", sizeof("timeout") - 1)) != NULL) {
timeout = atof(Z_STRVAL_P(param));
}
if ((param = zend_hash_str_find(Z_ARRVAL(params), "read_timeout", sizeof("read_timeout") - 1)) != NULL) {
read_timeout = atof(Z_STRVAL_P(param));
}
if ((param = zend_hash_str_find(Z_ARRVAL(params), "persistent", sizeof("persistent") - 1)) != NULL) {
persistent = (atol(Z_STRVAL_P(param)) == 1 ? 1 : 0);
}
if ((param = zend_hash_str_find(Z_ARRVAL(params), "persistent_id", sizeof("persistent_id") - 1)) != NULL) {
persistent_id = estrndup(Z_STRVAL_P(param), Z_STRLEN_P(param));
}
if ((param = zend_hash_str_find(Z_ARRVAL(params), "prefix", sizeof("prefix") - 1)) != NULL) {
prefix = estrndup(Z_STRVAL_P(param), Z_STRLEN_P(param));
}
if ((param = zend_hash_str_find(Z_ARRVAL(params), "auth", sizeof("auth") - 1)) != NULL) {
auth = estrndup(Z_STRVAL_P(param), Z_STRLEN_P(param));
}
if ((param = zend_hash_str_find(Z_ARRVAL(params), "database", sizeof("database") - 1)) != NULL) {
database = zval_get_long(param);
}
if ((param = zend_hash_str_find(Z_ARRVAL(params), "retry_interval", sizeof("retry_interval") - 1)) != NULL) {
retry_interval = zval_get_long(param);
}
zval_dtor(¶ms);
}
if ((url->path == NULL && url->host == NULL) || weight <= 0 || timeout <= 0) {
php_url_free(url);
if (persistent_id) efree(persistent_id);
if (prefix) efree(prefix);
if (auth) efree(auth);
redis_pool_free(pool TSRMLS_CC);
PS_SET_MOD_DATA(NULL);
return FAILURE;
}
RedisSock *redis_sock;
if(url->host) {
#if (PHP_VERSION_ID < 70300)
redis_sock = redis_sock_create(url->host, strlen(url->host), url->port, timeout, read_timeout, persistent, persistent_id, retry_interval, 0);
#else
redis_sock = redis_sock_create(ZSTR_VAL(url->host), ZSTR_LEN(url->host), url->port, timeout, read_timeout, persistent, persistent_id, retry_interval, 0);
#endif
} else { /* unix */
#if (PHP_VERSION_ID < 70300)
redis_sock = redis_sock_create(url->path, strlen(url->path), 0, timeout, read_timeout, persistent, persistent_id, retry_interval, 0);
#else
redis_sock = redis_sock_create(ZSTR_VAL(url->path), ZSTR_LEN(url->path), 0, timeout, read_timeout, persistent, persistent_id, retry_interval, 0);
#endif
}
redis_pool_add(pool, redis_sock, weight, database, prefix, auth TSRMLS_CC);
php_url_free(url);
}
}
if (pool->head) {
PS_SET_MOD_DATA(pool);
return SUCCESS;
}
return FAILURE;
}
/* }}} */
/* {{{ PS_CLOSE_FUNC
*/
PS_CLOSE_FUNC(redis)
{
redis_pool *pool = PS_GET_MOD_DATA();
if(pool){
redis_pool_free(pool TSRMLS_CC);
PS_SET_MOD_DATA(NULL);
}
return SUCCESS;
}
/* }}} */
static char *
redis_session_key(redis_pool_member *rpm, const char *key, int key_len, int *session_len) {
char *session;
char default_prefix[] = "PHPREDIS_SESSION:";
char *prefix = default_prefix;
size_t prefix_len = sizeof(default_prefix)-1;
if(rpm->prefix) {
prefix = rpm->prefix;
prefix_len = rpm->prefix_len;
}
/* build session key */
*session_len = key_len + prefix_len;
session = emalloc(*session_len);
memcpy(session, prefix, prefix_len);
memcpy(session + prefix_len, key, key_len);
return session;
}
/* {{{ PS_READ_FUNC
*/
PS_READ_FUNC(redis)
{
char *resp, *cmd;
int resp_len, cmd_len;
redis_pool *pool = PS_GET_MOD_DATA();
#if (PHP_MAJOR_VERSION < 7)
redis_pool_member *rpm = redis_pool_get_sock(pool, key TSRMLS_CC);
#else
redis_pool_member *rpm = redis_pool_get_sock(pool, ZSTR_VAL(key) TSRMLS_CC);
#endif
RedisSock *redis_sock = rpm?rpm->redis_sock:NULL;
if(!rpm || !redis_sock){
return FAILURE;
}
/* send GET command */
#if (PHP_MAJOR_VERSION < 7)
resp = redis_session_key(rpm, key, strlen(key), &resp_len);
#else
resp = redis_session_key(rpm, ZSTR_VAL(key), ZSTR_LEN(key), &resp_len);
#endif
cmd_len = REDIS_SPPRINTF(&cmd, "GET", "s", resp, resp_len);
efree(resp);
if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) {
efree(cmd);
return FAILURE;
}
efree(cmd);
/* Read response from Redis. If we get a NULL response from redis_sock_read
* this can indicate an error, OR a "NULL bulk" reply (empty session data)
* in which case we can reply with success. */
if ((resp = redis_sock_read(redis_sock, &resp_len TSRMLS_CC)) == NULL && resp_len != -1) {
return FAILURE;
}
#if (PHP_MAJOR_VERSION < 7)
if (resp_len < 0) {
*val = STR_EMPTY_ALLOC();
*vallen = 0;
} else {
*val = resp;
*vallen = resp_len;
}
#else
if (resp_len < 0) {
*val = ZSTR_EMPTY_ALLOC();
} else {
*val = zend_string_init(resp, resp_len, 0);
}
efree(resp);
#endif
return SUCCESS;
}
/* }}} */
/* {{{ PS_WRITE_FUNC
*/
PS_WRITE_FUNC(redis)
{
char *cmd, *response, *session;
int cmd_len, response_len, session_len;
redis_pool *pool = PS_GET_MOD_DATA();
#if (PHP_MAJOR_VERSION < 7)
redis_pool_member *rpm = redis_pool_get_sock(pool, key TSRMLS_CC);
#else
redis_pool_member *rpm = redis_pool_get_sock(pool, ZSTR_VAL(key) TSRMLS_CC);
#endif
RedisSock *redis_sock = rpm?rpm->redis_sock:NULL;
if(!rpm || !redis_sock){
return FAILURE;
}
/* send SET command */
#if (PHP_MAJOR_VERSION < 7)
session = redis_session_key(rpm, key, strlen(key), &session_len);
cmd_len = REDIS_SPPRINTF(&cmd, "SETEX", "sds", session, session_len,
INI_INT("session.gc_maxlifetime"), val, vallen);
#else
session = redis_session_key(rpm, ZSTR_VAL(key), ZSTR_LEN(key), &session_len);
cmd_len = REDIS_SPPRINTF(&cmd, "SETEX", "sds", session, session_len,
INI_INT("session.gc_maxlifetime"),
ZSTR_VAL(val), ZSTR_LEN(val));
#endif
efree(session);
if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) {
efree(cmd);
return FAILURE;
}
efree(cmd);
/* read response */
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) {
return FAILURE;
}
if(response_len == 3 && strncmp(response, "+OK", 3) == 0) {
efree(response);
return SUCCESS;
} else {
efree(response);
return FAILURE;
}
}
/* }}} */
/* {{{ PS_DESTROY_FUNC
*/
PS_DESTROY_FUNC(redis)
{
char *cmd, *response, *session;
int cmd_len, response_len, session_len;
redis_pool *pool = PS_GET_MOD_DATA();
#if (PHP_MAJOR_VERSION < 7)
redis_pool_member *rpm = redis_pool_get_sock(pool, key TSRMLS_CC);
#else
redis_pool_member *rpm = redis_pool_get_sock(pool, ZSTR_VAL(key) TSRMLS_CC);
#endif
RedisSock *redis_sock = rpm?rpm->redis_sock:NULL;
if(!rpm || !redis_sock){
return FAILURE;
}
/* send DEL command */
#if (PHP_MAJOR_VERSION < 7)
session = redis_session_key(rpm, key, strlen(key), &session_len);
#else
session = redis_session_key(rpm, ZSTR_VAL(key), ZSTR_LEN(key), &session_len);
#endif
cmd_len = REDIS_SPPRINTF(&cmd, "DEL", "s", session, session_len);
efree(session);
if(redis_sock_write(redis_sock, cmd, cmd_len TSRMLS_CC) < 0) {
efree(cmd);
return FAILURE;
}
efree(cmd);
/* read response */
if ((response = redis_sock_read(redis_sock, &response_len TSRMLS_CC)) == NULL) {
return FAILURE;
}
if(response_len == 2 && response[0] == ':' && (response[1] == '0' || response[1] == '1')) {
efree(response);
return SUCCESS;
} else {
efree(response);
return FAILURE;
}
}
/* }}} */
/* {{{ PS_GC_FUNC
*/
PS_GC_FUNC(redis)
{
return SUCCESS;
}
/* }}} */
/**
* Redis Cluster session handler functions
*/
/* Helper to extract timeout values */
static void session_conf_timeout(HashTable *ht_conf, const char *key, int key_len,
double *val)
{
zval *z_val;
if ((z_val = zend_hash_str_find(ht_conf, key, key_len - 1)) != NULL &&
Z_TYPE_P(z_val) == IS_STRING
) {
*val = atof(Z_STRVAL_P(z_val));
}
}
/* Simple helper to retreive a boolean (0 or 1) value from a string stored in our
* session.save_path variable. This is so the user can use 0, 1, or 'true',
* 'false' */
static void session_conf_bool(HashTable *ht_conf, char *key, int keylen,
int *retval) {
zval *z_val;
char *str;
int strlen;
/* See if we have the option, and it's a string */
if ((z_val = zend_hash_str_find(ht_conf, key, keylen - 1)) != NULL &&
Z_TYPE_P(z_val) == IS_STRING
) {
str = Z_STRVAL_P(z_val);
strlen = Z_STRLEN_P(z_val);
/* true/yes/1 are treated as true. Everything else is false */
*retval = (strlen == 4 && !strncasecmp(str, "true", 4)) ||
(strlen == 3 && !strncasecmp(str, "yes", 3)) ||
(strlen == 1 && !strncasecmp(str, "1", 1));
}
}
/* Prefix a session key */
static char *cluster_session_key(redisCluster *c, const char *key, int keylen,
int *skeylen, short *slot) {
char *skey;
*skeylen = keylen + ZSTR_LEN(c->flags->prefix);
skey = emalloc(*skeylen);
memcpy(skey, ZSTR_VAL(c->flags->prefix), ZSTR_LEN(c->flags->prefix));
memcpy(skey + ZSTR_LEN(c->flags->prefix), key, keylen);
*slot = cluster_hash_key(skey, *skeylen);
return skey;
}
PS_OPEN_FUNC(rediscluster) {
redisCluster *c;
zval z_conf, *z_val;
HashTable *ht_conf, *ht_seeds;
double timeout = 0, read_timeout = 0;
int persistent = 0;
int retval, prefix_len, failover = REDIS_FAILOVER_NONE;
char *prefix;
/* Parse configuration for session handler */
array_init(&z_conf);
sapi_module.treat_data(PARSE_STRING, estrdup(save_path), &z_conf TSRMLS_CC);
/* Sanity check that we're able to parse and have a seeds array */
if (Z_TYPE(z_conf) != IS_ARRAY ||
(z_val = zend_hash_str_find(Z_ARRVAL(z_conf), "seed", sizeof("seed") - 1)) == NULL ||
Z_TYPE_P(z_val) != IS_ARRAY)
{
zval_dtor(&z_conf);
return FAILURE;
}
/* Grab a copy of our config hash table and keep seeds array */
ht_conf = Z_ARRVAL(z_conf);
ht_seeds = Z_ARRVAL_P(z_val);
/* Grab timeouts if they were specified */
session_conf_timeout(ht_conf, "timeout", sizeof("timeout"), &timeout);
session_conf_timeout(ht_conf, "read_timeout", sizeof("read_timeout"), &read_timeout);
/* Grab persistent option */
session_conf_bool(ht_conf, "persistent", sizeof("persistent"), &persistent);
/* Sanity check on our timeouts */
if (timeout < 0 || read_timeout < 0) {
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"Can't set negative timeout values in session configuration");
zval_dtor(&z_conf);
return FAILURE;
}
/* Look for a specific prefix */
if ((z_val = zend_hash_str_find(ht_conf, "prefix", sizeof("prefix") - 1)) != NULL &&
Z_TYPE_P(z_val) == IS_STRING && Z_STRLEN_P(z_val) > 0
) {
prefix = Z_STRVAL_P(z_val);
prefix_len = Z_STRLEN_P(z_val);
} else {
prefix = "PHPREDIS_CLUSTER_SESSION:";
prefix_len = sizeof("PHPREDIS_CLUSTER_SESSION:")-1;
}
/* Look for a specific failover setting */
if ((z_val = zend_hash_str_find(ht_conf, "failover", sizeof("failover") - 1)) != NULL &&
Z_TYPE_P(z_val) == IS_STRING
) {
if (!strcasecmp(Z_STRVAL_P(z_val), "error")) {
failover = REDIS_FAILOVER_ERROR;
} else if (!strcasecmp(Z_STRVAL_P(z_val), "distribute")) {
failover = REDIS_FAILOVER_DISTRIBUTE;
}
}
c = cluster_create(timeout, read_timeout, failover, persistent);
if (!cluster_init_seeds(c, ht_seeds) && !cluster_map_keyspace(c TSRMLS_CC)) {
/* Set up our prefix */
c->flags->prefix = zend_string_init(prefix, prefix_len, 0);
PS_SET_MOD_DATA(c);
retval = SUCCESS;
} else {
cluster_free(c);
retval = FAILURE;
}
/* Cleanup */
zval_dtor(&z_conf);
return retval;
}
/* {{{ PS_READ_FUNC
*/
PS_READ_FUNC(rediscluster) {
redisCluster *c = PS_GET_MOD_DATA();
clusterReply *reply;
char *cmd, *skey;
int cmdlen, skeylen;
short slot;
/* Set up our command and slot information */
#if (PHP_MAJOR_VERSION < 7)
skey = cluster_session_key(c, key, strlen(key), &skeylen, &slot);
#else
skey = cluster_session_key(c, ZSTR_VAL(key), ZSTR_LEN(key), &skeylen, &slot);
#endif
cmdlen = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, "GET", "s", skey, skeylen);
efree(skey);
/* Attempt to kick off our command */
c->readonly = 1;
if (cluster_send_command(c,slot,cmd,cmdlen TSRMLS_CC)<0 || c->err) {
efree(cmd);
return FAILURE;
}
/* Clean up command */
efree(cmd);
/* Attempt to read reply */
reply = cluster_read_resp(c TSRMLS_CC);
if (!reply || c->err) {
if (reply) cluster_free_reply(reply, 1);
return FAILURE;
}
/* Push reply value to caller */
#if (PHP_MAJOR_VERSION < 7)
if (reply->str == NULL) {
*val = STR_EMPTY_ALLOC();
*vallen = 0;
} else {
*val = reply->str;
*vallen = reply->len;
}
#else
if (reply->str == NULL) {
*val = ZSTR_EMPTY_ALLOC();
} else {
*val = zend_string_init(reply->str, reply->len, 0);
}
#endif
/* Clean up */
cluster_free_reply(reply, 0);
/* Success! */
return SUCCESS;
}
/* {{{ PS_WRITE_FUNC
*/
PS_WRITE_FUNC(rediscluster) {
redisCluster *c = PS_GET_MOD_DATA();
clusterReply *reply;
char *cmd, *skey;
int cmdlen, skeylen;
short slot;
/* Set up command and slot info */
#if (PHP_MAJOR_VERSION < 7)
skey = cluster_session_key(c, key, strlen(key), &skeylen, &slot);
cmdlen = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, "SETEX", "sds", skey,
skeylen, INI_INT("session.gc_maxlifetime"), val,
vallen);
#else
skey = cluster_session_key(c, ZSTR_VAL(key), ZSTR_LEN(key), &skeylen, &slot);
cmdlen = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, "SETEX", "sds", skey,
skeylen, INI_INT("session.gc_maxlifetime"),
ZSTR_VAL(val), ZSTR_LEN(val));
#endif
efree(skey);
/* Attempt to send command */
c->readonly = 0;
if (cluster_send_command(c,slot,cmd,cmdlen TSRMLS_CC)<0 || c->err) {
efree(cmd);
return FAILURE;
}
/* Clean up our command */
efree(cmd);
/* Attempt to read reply */
reply = cluster_read_resp(c TSRMLS_CC);
if (!reply || c->err) {
if (reply) cluster_free_reply(reply, 1);
return FAILURE;
}
/* Clean up*/
cluster_free_reply(reply, 1);
return SUCCESS;
}
/* {{{ PS_DESTROY_FUNC(rediscluster)
*/
PS_DESTROY_FUNC(rediscluster) {
redisCluster *c = PS_GET_MOD_DATA();
clusterReply *reply;
char *cmd, *skey;
int cmdlen, skeylen;
short slot;
/* Set up command and slot info */
#if (PHP_MAJOR_VERSION < 7)
skey = cluster_session_key(c, key, strlen(key), &skeylen, &slot);
#else
skey = cluster_session_key(c, ZSTR_VAL(key), ZSTR_LEN(key), &skeylen, &slot);
#endif
cmdlen = redis_spprintf(NULL, NULL TSRMLS_CC, &cmd, "DEL", "s", skey, skeylen);
efree(skey);
/* Attempt to send command */
if (cluster_send_command(c,slot,cmd,cmdlen TSRMLS_CC)<0 || c->err) {
efree(cmd);
return FAILURE;
}
/* Clean up our command */
efree(cmd);
/* Attempt to read reply */
reply = cluster_read_resp(c TSRMLS_CC);
if (!reply || c->err) {
if (reply) cluster_free_reply(reply, 1);
return FAILURE;
}
/* Clean up our reply */
cluster_free_reply(reply, 1);
return SUCCESS;
}
/* {{{ PS_CLOSE_FUNC
*/
PS_CLOSE_FUNC(rediscluster)
{
redisCluster *c = PS_GET_MOD_DATA();
if (c) {
cluster_free(c);
PS_SET_MOD_DATA(NULL);
}
return SUCCESS;
}
/* {{{ PS_GC_FUNC
*/
PS_GC_FUNC(rediscluster) {
return SUCCESS;
}
#endif
/* vim: set tabstop=4 expandtab: */
redis-3.1.6/redis_session.h 0000644 0001750 0000012 00000000646 13223116600 016430 0 ustar pyatsukhnenko wheel #ifndef REDIS_SESSION_H
#define REDIS_SESSION_H
#ifdef PHP_SESSION
#include "ext/session/php_session.h"
PS_OPEN_FUNC(redis);
PS_CLOSE_FUNC(redis);
PS_READ_FUNC(redis);
PS_WRITE_FUNC(redis);
PS_DESTROY_FUNC(redis);
PS_GC_FUNC(redis);
PS_OPEN_FUNC(rediscluster);
PS_CLOSE_FUNC(rediscluster);
PS_READ_FUNC(rediscluster);
PS_WRITE_FUNC(rediscluster);
PS_DESTROY_FUNC(rediscluster);
PS_GC_FUNC(rediscluster);
#endif
#endif