cd src; make
The compilation will produce a redis-server binary.
-Copy this file where you want.
+
+To install Redis, use
+
+ make install
+
+and all the binaries will be installed on /usr/local/bin.
+
+Alternatively:
+
+ make PREFIX=/some/other/directory
+
+to have the binaries in /some/other/directory/bin.
Run the server using the following command line:
-Check the 'doc' directory. doc/README.html is a good starting point :)
+Where to find complete Redis documentation?
+-------------------------------------------
+
+This README is just a fast "quick start" document. You can find more detailed
+documentation here:
+
+1) http://code.google.com/p/redis
+2) Check the 'doc' directory. doc/README.html is a good starting point :)
+
+Building Redis
+--------------
+
+It is as simple as:
+
+ % make
+
+Redis is just a single binary, but if you want to install it you can use
+the "make install" target that will copy the binary in /usr/local/bin
+for default. You can also use "make PREFIX=/some/other/directory install"
+if you wish to use a different destination.
+
+You can run a 32 bit Redis binary using:
+
+ % make 32bit
+
+After you build Redis is a good idea to test it, using:
+
+ % make test
+
+Running Redis
+-------------
+
+To run Redis with the default configuration just type:
+
+ % cd src
+ % ./redis-server
+
+If you want to provide your redis.conf, you have to run it using an additional
+parameter (the path of the configuration file):
+
+ % cd src
+ % ./redis-server /path/to/redis.conf
+
+Playing with Redis
+------------------
+
+You can use redis-cli to play with Redis. Start a redis-server instance,
+then in another terminal try the following:
+
+ % cd src
+ % ./redis-cli
+ redis> ping
+ PONG
+ redis> set foo bar
+ OK
+ redis> get foo
+ "bar"
+ redis> incr mycounter
+ (integer) 1
+ redis> incr mycounter
+ (integer) 2
+ redis>
+
+You can find the list of all the available commands here:
+
+ http://code.google.com/p/redis/wiki/CommandReference
+
+Enjoy!
+
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
-<b>BlpopCommand: Contents</b><br> <a href="#BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis >">BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis ></a><br> <a href="#BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis >">BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis ></a><br> <a href="#Non blocking behavior">Non blocking behavior</a><br> <a href="#Blocking behavior">Blocking behavior</a><br> <a href="#Multiple clients blocking for the same keys">Multiple clients blocking for the same keys</a><br> <a href="#Return value">Return value</a>
+<b>BlpopCommand: Contents</b><br> <a href="#BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis >">BLPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis ></a><br> <a href="#BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis >">BRPOP _key1_ _key2_ ... _keyN_ _timeout_ (Redis ></a><br> <a href="#Non blocking behavior">Non blocking behavior</a><br> <a href="#Blocking behavior">Blocking behavior</a><br> <a href="#Multiple clients blocking for the same keys">Multiple clients blocking for the same keys</a><br> <a href="#blocking POP inside a MULTI/EXEC transaction">blocking POP inside a MULTI/EXEC transaction</a><br> <a href="#Return value">Return value</a>
</div>
<h1 class="wikiname">BlpopCommand</h1>
<h2><a name="Blocking behavior">Blocking behavior</a></h2><blockquote>If none of the specified keys exist or contain non empty lists, BLPOPblocks until some other client performs a <a href="RpushCommand.html">LPUSH</a> oran <a href="RpushCommand.html">RPUSH</a> operation against one of the lists.</blockquote>
<blockquote>Once new data is present on one of the lists, the client finally returnswith the name of the key unblocking it and the popped value.</blockquote>
<blockquote>When blocking, if a non-zero timeout is specified, the client will unblockreturning a nil special value if the specified amount of seconds passedwithout a push operation against at least one of the specified keys.</blockquote>
-<blockquote>A timeout of zero means instead to block forever.</blockquote>
+<blockquote>The timeout argument is interpreted as an integer value. A timeout of zero means instead to block forever.</blockquote>
<h2><a name="Multiple clients blocking for the same keys">Multiple clients blocking for the same keys</a></h2><blockquote>Multiple clients can block for the same key. They are put intoa queue, so the first to be served will be the one that started to waitearlier, in a first-blpopping first-served fashion.</blockquote>
+<h2><a name="blocking POP inside a MULTI/EXEC transaction">blocking POP inside a MULTI/EXEC transaction</a></h2><blockquote>BLPOP and BRPOP can be used with pipelining (sending multiple commands and reading the replies in batch), but it does not make sense to use BLPOP or BRPOP inside a MULTI/EXEC block (a Redis transaction).</blockquote>
+<blockquote>The behavior of BLPOP inside MULTI/EXEC when the list is empty is to return a multi-bulk nil reply, exactly what happens when the timeout is reached. If you like science fiction, think at it like if inside MULTI/EXEC the time will flow at infinite speed :) </blockquote>
<h2><a name="Return value">Return value</a></h2><blockquote>BLPOP returns a two-elements array via a multi bulk reply in order to returnboth the unblocking key and the popped value.</blockquote>
<blockquote>When a non-zero timeout is specified, and the BLPOP operation timed out,the return value is a nil multi bulk reply. Most client values will returnfalse or nil accordingly to the programming language used.</blockquote>
<a href="ReplyTypes.html">Multi bulk reply</a>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
-<b>CommandReference: Contents</b><br> <a href="#Connection handling">Connection handling</a><br> <a href="#Commands operating on all the kind of values">Commands operating on all the kind of values</a><br> <a href="#Commands operating on string values">Commands operating on string values</a><br> <a href="#Commands operating on lists">Commands operating on lists</a><br> <a href="#Commands operating on sets">Commands operating on sets</a><br> <a href="#Commands operating on sorted sets (zsets, Redis version >">Commands operating on sorted sets (zsets, Redis version ></a><br> <a href="#Commands operating on hashes">Commands operating on hashes</a><br> <a href="#Sorting">Sorting</a><br> <a href="#Transactions">Transactions</a><br> <a href="#Publish/Subscribe">Publish/Subscribe</a><br> <a href="#Persistence control commands">Persistence control commands</a><br> <a href="#Remote server control commands">Remote server control commands</a>
+<b>CommandReference: Contents</b><br> <a href="#Categorized Command List">Categorized Command List</a><br> <a href="#Connection handling">Connection handling</a><br> <a href="#Commands operating on all value types">Commands operating on all value types</a><br> <a href="#Commands operating on string values">Commands operating on string values</a><br> <a href="#Commands operating on lists">Commands operating on lists</a><br> <a href="#Commands operating on sets">Commands operating on sets</a><br> <a href="#Commands operating on sorted zsets (sorted sets)">Commands operating on sorted zsets (sorted sets)</a><br> <a href="#Commands operating on hashes">Commands operating on hashes</a><br> <a href="#Sorting">Sorting</a><br> <a href="#Transactions">Transactions</a><br> <a href="#Publish/Subscribe">Publish/Subscribe</a><br> <a href="#Persistence control commands">Persistence control commands</a><br> <a href="#Remote server control commands">Remote server control commands</a>
</div>
<h1 class="wikiname">CommandReference</h1>
</div>
<div class="narrow">
- = Redis Command Reference =<br/><br/>Every command name links to a specific wiki page describing the behavior of the command.<h2><a name="Connection handling">Connection handling</a></h2><ul><li> <a href="QuitCommand.html">QUIT</a> <code name="code" class="python">close the connection</code></li><li> <a href="AuthCommand.html">AUTH</a> <code name="code" class="python">simple password authentication if enabled</code></li></ul>
-<h2><a name="Commands operating on all the kind of values">Commands operating on all the kind of values</a></h2><ul><li> <a href="ExistsCommand.html">EXISTS</a> <i>key</i> <code name="code" class="python">test if a key exists</code></li><li> <a href="DelCommand.html">DEL</a> <i>key</i> <code name="code" class="python">delete a key</code></li><li> <a href="TypeCommand.html">TYPE</a> <i>key</i> <code name="code" class="python">return the type of the value stored at key</code></li><li> <a href="KeysCommand.html">KEYS</a> <i>pattern</i> <code name="code" class="python">return all the keys matching a given pattern</code></li><li> <a href="RandomkeyCommand.html">RANDOMKEY</a> <code name="code" class="python">return a random key from the key space</code></li><li> <a href="RenameCommand.html">RENAME</a> <i>oldname</i> <i>newname</i> <code name="code" class="python">rename the old key in the new one, destroing the newname key if it already exists</code></li><li> <a href="RenamenxCommand.html">RENAMENX</a> <i>oldname</i> <i>newname</i> <code name="code" class="python">rename the old key in the new one, if the newname key does not already exist</code></li><li> <a href="DbsizeCommand.html">DBSIZE</a> <code name="code" class="python">return the number of keys in the current db</code></li><li> <a href="ExpireCommand.html">EXPIRE</a> <code name="code" class="python">set a time to live in seconds on a key</code></li><li> <a href="TtlCommand.html">TTL</a> <code name="code" class="python">get the time to live in seconds of a key</code></li><li> <a href="SelectCommand.html">SELECT</a> <i>index</i> <code name="code" class="python">Select the DB having the specified index</code></li><li> <a href="MoveCommand.html">MOVE</a> <i>key</i> <i>dbindex</i> <code name="code" class="python">Move the key from the currently selected DB to the DB having as index dbindex</code></li><li> <a href="FlushdbCommand.html">FLUSHDB</a> <code name="code" class="python">Remove all the keys of the currently selected DB</code></li><li> <a href="FlushallCommand.html">FLUSHALL</a> <code name="code" class="python">Remove all the keys from all the databases</code></li></ul>
-<h2><a name="Commands operating on string values">Commands operating on string values</a></h2><ul><li> <a href="SetCommand.html">SET</a> <i>key</i> <i>value</i> <code name="code" class="python">set a key to a string value</code></li><li> <a href="GetCommand.html">GET</a> <i>key</i> <code name="code" class="python">return the string value of the key</code></li><li> <a href="GetsetCommand.html">GETSET</a> <i>key</i> <i>value</i> <code name="code" class="python">set a key to a string returning the old value of the key</code></li><li> <a href="MgetCommand.html">MGET</a> <i>key1</i> <i>key2</i> ... <i>keyN</i> <code name="code" class="python">multi-get, return the strings values of the keys</code></li><li> <a href="SetnxCommand.html">SETNX</a> <i>key</i> <i>value</i> <code name="code" class="python">set a key to a string value if the key does not exist</code></li><li> <a href="SetexCommand.html">SETEX</a> <i>key</i> <i>time</i> <i>value</i> <code name="code" class="python">Set+Expire combo command</code></li><li> <a href="MsetCommand.html">MSET</a> <i>key1</i> <i>value1</i> <i>key2</i> <i>value2</i> ... <i>keyN</i> <i>valueN</i> <code name="code" class="python">set a multiple keys to multiple values in a single atomic operation</code></li><li> <a href="MsetCommand.html">MSETNX</a> <i>key1</i> <i>value1</i> <i>key2</i> <i>value2</i> ... <i>keyN</i> <i>valueN</i> <code name="code" class="python">set a multiple keys to multiple values in a single atomic operation if none of the keys already exist</code></li><li> <a href="IncrCommand.html">INCR</a> <i>key</i> <code name="code" class="python">increment the integer value of key</code></li><li> <a href="IncrCommand.html">INCRBY</a> <i>key</i> <i>integer</i><code name="code" class="python"> increment the integer value of key by integer</code></li><li> <a href="IncrCommand.html">DECR</a> <i>key</i> <code name="code" class="python">decrement the integer value of key</code></li><li> <a href="IncrCommand.html">DECRBY</a> <i>key</i> <i>integer</i> <code name="code" class="python">decrement the integer value of key by integer</code></li><li> <a href="AppendCommand.html">APPEND</a> <i>key</i> <i>value</i> <code name="code" class="python">append the specified string to the string stored at key</code></li><li> <a href="SubstrCommand.html">SUBSTR</a> <i>key</i> <i>start</i> <i>end</i> <code name="code" class="python">return a substring out of a larger string</code></li></ul>
-<h2><a name="Commands operating on lists">Commands operating on lists</a></h2><ul><li> <a href="RpushCommand.html">RPUSH</a> <i>key</i> <i>value</i> <code name="code" class="python">Append an element to the tail of the List value at key</code></li><li> <a href="RpushCommand.html">LPUSH</a> <i>key</i> <i>value</i> <code name="code" class="python">Append an element to the head of the List value at key</code></li><li> <a href="LlenCommand.html">LLEN</a> <i>key</i> <code name="code" class="python">Return the length of the List value at key</code></li><li> <a href="LrangeCommand.html">LRANGE</a> <i>key</i> <i>start</i> <i>end</i> <code name="code" class="python">Return a range of elements from the List at key</code></li><li> <a href="LtrimCommand.html">LTRIM</a> <i>key</i> <i>start</i> <i>end</i> <code name="code" class="python">Trim the list at key to the specified range of elements</code></li><li> <a href="LindexCommand.html">LINDEX</a> <i>key</i> <i>index</i> <code name="code" class="python">Return the element at index position from the List at key</code></li><li> <a href="LsetCommand.html">LSET</a> <i>key</i> <i>index</i> <i>value</i> <code name="code" class="python">Set a new value as the element at index position of the List at key</code></li><li> <a href="LremCommand.html">LREM</a> <i>key</i> <i>count</i> <i>value</i> <code name="code" class="python">Remove the first-N, last-N, or all the elements matching value from the List at key</code></li><li> <a href="LpopCommand.html">LPOP</a> <i>key</i> <code name="code" class="python">Return and remove (atomically) the first element of the List at key</code></li><li> <a href="LpopCommand.html">RPOP</a> <i>key</i> <code name="code" class="python">Return and remove (atomically) the last element of the List at key</code></li><li> <a href="BlpopCommand.html">BLPOP</a> <i>key1</i> <i>key2</i> ... <i>keyN</i> <i>timeout</i> <code name="code" class="python">Blocking LPOP</code></li><li> <a href="BlpopCommand.html">BRPOP</a> <i>key1</i> <i>key2</i> ... <i>keyN</i> <i>timeout</i> <code name="code" class="python">Blocking RPOP</code></li><li> <a href="RpoplpushCommand.html">RPOPLPUSH</a> <i>srckey</i> <i>dstkey</i> <code name="code" class="python">Return and remove (atomically) the last element of the source List stored at _srckey_ and push the same element to the destination List stored at _dstkey_</code></li></ul>
-<h2><a name="Commands operating on sets">Commands operating on sets</a></h2><ul><li> <a href="SaddCommand.html">SADD</a> <i>key</i> <i>member</i> <code name="code" class="python">Add the specified member to the Set value at key</code></li><li> <a href="SremCommand.html">SREM</a> <i>key</i> <i>member</i> <code name="code" class="python">Remove the specified member from the Set value at key</code></li><li> <a href="SpopCommand.html">SPOP</a> <i>key</i> <code name="code" class="python">Remove and return (pop) a random element from the Set value at key</code></li><li> <a href="SmoveCommand.html">SMOVE</a> <i>srckey</i> <i>dstkey</i> <i>member</i> <code name="code" class="python">Move the specified member from one Set to another atomically</code></li><li> <a href="ScardCommand.html">SCARD</a> <i>key</i> <code name="code" class="python">Return the number of elements (the cardinality) of the Set at key</code></li><li> <a href="SismemberCommand.html">SISMEMBER</a> <i>key</i> <i>member</i> <code name="code" class="python">Test if the specified value is a member of the Set at key</code></li><li> <a href="SinterCommand.html">SINTER</a> <i>key1</i> <i>key2</i> ... <i>keyN</i> <code name="code" class="python">Return the intersection between the Sets stored at key1, key2, ..., keyN</code></li><li> <a href="SinterstoreCommand.html">SINTERSTORE</a> <i>dstkey</i> <i>key1</i> <i>key2</i> ... <i>keyN</i> <code name="code" class="python">Compute the intersection between the Sets stored at key1, key2, ..., keyN, and store the resulting Set at dstkey</code></li><li> <a href="SunionCommand.html">SUNION</a> <i>key1</i> <i>key2</i> ... <i>keyN</i> <code name="code" class="python">Return the union between the Sets stored at key1, key2, ..., keyN</code></li><li> <a href="SunionstoreCommand.html">SUNIONSTORE</a> <i>dstkey</i> <i>key1</i> <i>key2</i> ... <i>keyN</i> <code name="code" class="python">Compute the union between the Sets stored at key1, key2, ..., keyN, and store the resulting Set at dstkey</code></li><li> <a href="SdiffCommand.html">SDIFF</a> <i>key1</i> <i>key2</i> ... <i>keyN</i> <code name="code" class="python">Return the difference between the Set stored at key1 and all the Sets key2, ..., keyN</code></li><li> <a href="SdiffstoreCommand.html">SDIFFSTORE</a> <i>dstkey</i> <i>key1</i> <i>key2</i> ... <i>keyN</i> <code name="code" class="python">Compute the difference between the Set key1 and all the Sets key2, ..., keyN, and store the resulting Set at dstkey</code></li><li> <a href="SmembersCommand.html">SMEMBERS</a> <i>key</i> <code name="code" class="python">Return all the members of the Set value at key</code></li><li> <a href="SrandmemberCommand.html">SRANDMEMBER</a> <i>key</i> <code name="code" class="python">Return a random member of the Set value at key</code></li></ul>
-<h2><a name="Commands operating on sorted sets (zsets, Redis version >">Commands operating on sorted sets (zsets, Redis version ></a></h2> 1.1) ==<br/><br/><ul><li> <a href="ZaddCommand.html">ZADD</a> <i>key</i> <i>score</i> <i>member</i> <code name="code" class="python">Add the specified member to the Sorted Set value at key or update the score if it already exist</code></li><li> <a href="ZremCommand.html">ZREM</a> <i>key</i> <i>member</i> <code name="code" class="python">Remove the specified member from the Sorted Set value at key</code></li><li> <a href="ZincrbyCommand.html">ZINCRBY</a> <i>key</i> <i>increment</i> <i>member</i> <code name="code" class="python">If the member already exists increment its score by _increment_, otherwise add the member setting _increment_ as score</code></li><li> <a href="ZrankCommand.html">ZRANK</a> <i>key</i> <i>member</i> <code name="code" class="python">Return the rank (or index) or _member_ in the sorted set at _key_, with scores being ordered from low to high</code></li><li> <a href="ZrankCommand.html">ZREVRANK</a> <i>key</i> <i>member</i> <code name="code" class="python">Return the rank (or index) or _member_ in the sorted set at _key_, with scores being ordered from high to low</code></li><li> <a href="ZrangeCommand.html">ZRANGE</a> <i>key</i> <i>start</i> <i>end</i> <code name="code" class="python">Return a range of elements from the sorted set at key</code></li><li> <a href="ZrangeCommand.html">ZREVRANGE</a> <i>key</i> <i>start</i> <i>end</i> <code name="code" class="python">Return a range of elements from the sorted set at key, exactly like ZRANGE, but the sorted set is ordered in traversed in reverse order, from the greatest to the smallest score</code></li><li> <a href="ZrangebyscoreCommand.html">ZRANGEBYSCORE</a> <i>key</i> <i>min</i> <i>max</i> <code name="code" class="python">Return all the elements with score >= min and score <= max (a range query) from the sorted set</code></li><li> <a href="ZcardCommand.html">ZCARD</a> <i>key</i> <code name="code" class="python">Return the cardinality (number of elements) of the sorted set at key</code></li><li> <a href="ZscoreCommand.html">ZSCORE</a> <i>key</i> <i>element</i> <code name="code" class="python">Return the score associated with the specified element of the sorted set at key</code></li><li> <a href="ZremrangebyrankCommand.html">ZREMRANGEBYRANK</a> <i>key</i> <i>min</i> <i>max</i> <code name="code" class="python">Remove all the elements with rank >= min and rank <= max from the sorted set</code></li><li> <a href="ZremrangebyscoreCommand.html">ZREMRANGEBYSCORE</a> <i>key</i> <i>min</i> <i>max</i> <code name="code" class="python">Remove all the elements with score >= min and score <= max from the sorted set</code></li><li> <a href="ZunionstoreCommand.html">ZUNIONSTORE / ZINTERSTORE</a> <i>dstkey</i> <i>N</i> <i>key1</i> ... <i>keyN</i> WEIGHTS <i>w1</i> ... <i>wN</i> AGGREGATE SUM|MIN|MAX <code name="code" class="python">Perform a union or intersection over a number of sorted sets with optional weight and aggregate</code></li></ul>
-<h2><a name="Commands operating on hashes">Commands operating on hashes</a></h2><ul><li> <a href="HsetCommand.html">HSET</a> <i>key</i> <i>field</i> <i>value</i> <code name="code" class="python">Set the hash field to the specified value. Creates the hash if needed.</code></li><li> <a href="HgetCommand.html">HGET</a> <i>key</i> <i>field</i> <code name="code" class="python">Retrieve the value of the specified hash field.</code></li><li> <a href="HmsetCommand.html">HMSET</a> <i>key</i> <i>field1</i> <i>value1</i> ... <i>fieldN</i> <i>valueN</i> <code name="code" class="python">Set the hash fields to their respective values.</code></li><li> <a href="HincrbyCommand.html">HINCRBY</a> <i>key</i> <i>field</i> <i>integer</i> <code name="code" class="python">Increment the integer value of the hash at _key_ on _field_ with _integer_.</code></li><li> <a href="HexistsCommand.html">HEXISTS</a> <i>key</i> <i>field</i> <code name="code" class="python">Test for existence of a specified field in a hash</code></li><li> <a href="HdelCommand.html">HDEL</a> <i>key</i> <i>field</i> <code name="code" class="python">Remove the specified field from a hash</code></li><li> <a href="HlenCommand.html">HLEN</a> <i>key</i> <code name="code" class="python">Return the number of items in a hash.</code></li><li> <a href="HgetallCommand.html">HKEYS</a> <i>key</i> <code name="code" class="python">Return all the fields in a hash.</code></li><li> <a href="HgetallCommand.html">HVALS</a> <i>key</i> <code name="code" class="python">Return all the values in a hash.</code></li><li> <a href="HgetallCommand.html">HGETALL</a> <i>key</i> <code name="code" class="python">Return all the fields and associated values in a hash.</code></li></ul>
-<h2><a name="Sorting">Sorting</a></h2><ul><li> <a href="SortCommand.html">SORT</a> <i>key</i> BY <i>pattern</i> LIMIT <i>start</i> <i>end</i> GET <i>pattern</i> ASC|DESC ALPHA <code name="code" class="python">Sort a Set or a List accordingly to the specified parameters</code></li></ul>
-<h2><a name="Transactions">Transactions</a></h2><ul><li> <a href="MultiExecCommand.html">MULTI/EXEC/DISCARD</a> <code name="code" class="python">Redis atomic transactions</code></li></ul>
-<h2><a name="Publish/Subscribe">Publish/Subscribe</a></h2><ul><li> <a href="PublishSubscribe.html">SUBSCRIBE/UNSUBSCRIBE/PUBLISH</a> <code name="code" class="python">Redis Public/Subscribe messaging paradigm implementation</code></li></ul>
-<h2><a name="Persistence control commands">Persistence control commands</a></h2><ul><li> <a href="SaveCommand.html">SAVE</a> <code name="code" class="python">Synchronously save the DB on disk</code></li><li> <a href="BgsaveCommand.html">BGSAVE</a> <code name="code" class="python">Asynchronously save the DB on disk</code></li><li> <a href="LastsaveCommand.html">LASTSAVE</a> <code name="code" class="python">Return the UNIX time stamp of the last successfully saving of the dataset on disk</code></li><li> <a href="ShutdownCommand.html">SHUTDOWN</a> <code name="code" class="python">Synchronously save the DB on disk, then shutdown the server</code></li><li> <a href="BgrewriteaofCommand.html">BGREWRITEAOF</a> <code name="code" class="python">Rewrite the append only file in background when it gets too big</code></li></ul>
-<h2><a name="Remote server control commands">Remote server control commands</a></h2><ul><li> <a href="InfoCommand.html">INFO</a> <code name="code" class="python">Provide information and statistics about the server</code></li><li> <a href="MonitorCommand.html">MONITOR</a> <code name="code" class="python">Dump all the received requests in real time</code></li><li> <a href="SlaveofCommand.html">SLAVEOF</a> <code name="code" class="python">Change the replication settings</code></li><li> <a href="ConfigCommand.html">CONFIG</a> <code name="code" class="python">Configure a Redis server at runtime</code></li></ul>
+ = Redis Command Reference =<br/><br/>Every command name links to a specific wiki page describing the behavior of the command.<h2><a name="Categorized Command List">Categorized Command List</a></h2><h2><a name="Connection handling">Connection handling</a></h2><table><tr><td style="border: 1px solid #aaa; padding: 5px;"> <b>Command</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Parameters</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Description</b> </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="QuitCommand.html">QUIT</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> - </td><td style="border: 1px solid #aaa; padding: 5px;"> close the connection </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="AuthCommand.html">AUTH</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>password</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> simple password authentication if enabled </td></tr></table>
+<h2><a name="Commands operating on all value types">Commands operating on all value types</a></h2><table><tr><td style="border: 1px solid #aaa; padding: 5px;"> <b>Command</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Parameters</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Description</b> </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ExistsCommand.html">EXISTS</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> test if a key exists </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="DelCommand.html">DEL</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> delete a key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="TypeCommand.html">TYPE</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> return the type of the value stored at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="KeysCommand.html">KEYS</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>pattern</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> return all the keys matching a given pattern </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="RandomkeyCommand.html">RANDOMKEY</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> - </td><td style="border: 1px solid #aaa; padding: 5px;"> return a random key from the key space </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="RenameCommand.html">RENAME</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>oldname</i> <i>newname</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> rename the old key in the new one, destroying the newname key if it already exists </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="RenamenxCommand.html">RENAMENX</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>oldname</i> <i>newname</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> rename the <i>oldname</i> key to <i>newname</i>, if the <i>newname</i> key does not already exist </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="DbsizeCommand.html">DBSIZE</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> - </td><td style="border: 1px solid #aaa; padding: 5px;"> return the number of keys in the current db </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ExpireCommand.html">EXPIRE</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> - </td><td style="border: 1px solid #aaa; padding: 5px;"> set a time to live in seconds on a key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ExpireCommand.html">PERSIST</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> - </td><td style="border: 1px solid #aaa; padding: 5px;"> remove the expire from a key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="TtlCommand.html">TTL</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> - </td><td style="border: 1px solid #aaa; padding: 5px;"> get the time to live in seconds of a key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SelectCommand.html">SELECT</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>index</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Select the DB with the specified index </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="MoveCommand.html">MOVE</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>dbindex</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Move the key from the currently selected DB to the <i>dbindex</i> DB </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="FlushdbCommand.html">FLUSHDB</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> - </td><td style="border: 1px solid #aaa; padding: 5px;"> Remove all the keys from the currently selected DB </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="FlushallCommand.html">FLUSHALL</a></td><td style="border: 1px solid #aaa; padding: 5px;"> - </td><td style="border: 1px solid #aaa; padding: 5px;"> Remove all the keys from all the databases </td></tr></table>
+<h2><a name="Commands operating on string values">Commands operating on string values</a></h2><table><tr><td style="border: 1px solid #aaa; padding: 5px;"> <b>Command</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Parameters</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Description</b> </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SetCommand.html">SET</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>value</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Set a <i>key</i> to a string <i>value</i> </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="GetCommand.html">GET</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return the string value of the <i>key</i> </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="GetsetCommand.html">GETSET</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>value</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Set a key to a string returning the old value of the key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="MgetCommand.html">MGET</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key1</i> <i>key2</i> ... <i>keyN</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Multi-get, return the strings values of the keys </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SetnxCommand.html">SETNX</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>value</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Set a key to a string value if the key does not exist </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SetexCommand.html">SETEX</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>time</i> <i>value</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Set+Expire combo command </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="MsetCommand.html">MSET</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key1</i> <i>value1</i> <i>key2</i> <i>value2</i> ... <i>keyN</i> <i>valueN</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Set multiple keys to multiple values in a single atomic operation </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="MsetCommand.html">MSETNX</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key1</i> <i>value1</i> <i>key2</i> <i>value2</i> ... <i>keyN</i> <i>valueN</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Set multiple keys to multiple values in a single atomic operation if none of the keys already exist </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="IncrCommand.html">INCR</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Increment the integer value of key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="IncrCommand.html">INCRBY</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>integer</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Increment the integer value of <i>key</i> by <i>integer</i> </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="IncrCommand.html">DECR</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Decrement the integer value of key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="IncrCommand.html">DECRBY</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>integer</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Decrement the integer value of <i>key</i> by <i>integer</i> </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="AppendCommand.html">APPEND</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>value</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Append the specified string to the string stored at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SubstrCommand.html">SUBSTR</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>start</i> <i>end</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return a substring of a larger string </td></tr></table>
+<h2><a name="Commands operating on lists">Commands operating on lists</a></h2><table><tr><td style="border: 1px solid #aaa; padding: 5px;"> <b>Command</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Parameters</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Description</b> </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="RpushCommand.html">RPUSH</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>value</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Append an element to the tail of the List value at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="RpushCommand.html">LPUSH</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>value</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Append an element to the head of the List value at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="LlenCommand.html">LLEN</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return the length of the List value at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="LrangeCommand.html">LRANGE</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>start</i> <i>end</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return a range of elements from the List at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="LtrimCommand.html">LTRIM</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>start</i> <i>end</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Trim the list at key to the specified range of elements </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="LindexCommand.html">LINDEX</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>index</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return the element at index position from the List at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="LsetCommand.html">LSET</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>index</i> <i>value</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Set a new value as the element at index position of the List at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="LremCommand.html">LREM</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>count</i> <i>value</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Remove the first-N, last-N, or all the elements matching value from the List at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="LpopCommand.html">LPOP</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return and remove (atomically) the first element of the List at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="LpopCommand.html">RPOP</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return and remove (atomically) the last element of the List at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="BlpopCommand.html">BLPOP</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key1</i> <i>key2</i> ... <i>keyN</i> <i>timeout</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Blocking LPOP </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="BlpopCommand.html">BRPOP</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key1</i> <i>key2</i> ... <i>keyN</i> <i>timeout</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Blocking RPOP </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="RpoplpushCommand.html">RPOPLPUSH</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>srckey</i> <i>dstkey</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return and remove (atomically) the last element of the source List stored at <i>srckey</i> and push the same element to the destination List stored at <i>dstkey</i> </td></tr></table>
+<h2><a name="Commands operating on sets">Commands operating on sets</a></h2><table><tr><td style="border: 1px solid #aaa; padding: 5px;"> <b>Command</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Parameters</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Description</b> </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SaddCommand.html">SADD</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>member</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Add the specified member to the Set value at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SremCommand.html">SREM</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>member</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Remove the specified member from the Set value at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SpopCommand.html">SPOP</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Remove and return (pop) a random element from the Set value at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SmoveCommand.html">SMOVE</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>srckey</i> <i>dstkey</i> <i>member</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Move the specified member from one Set to another atomically </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ScardCommand.html">SCARD</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return the number of elements (the cardinality) of the Set at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SismemberCommand.html">SISMEMBER</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>member</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Test if the specified value is a member of the Set at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SinterCommand.html">SINTER</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key1</i> <i>key2</i> ... <i>keyN</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return the intersection between the Sets stored at key1, key2, ..., keyN </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SinterstoreCommand.html">SINTERSTORE</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>dstkey</i> <i>key1</i> <i>key2</i> ... <i>keyN</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Compute the intersection between the Sets stored at key1, key2, ..., keyN, and store the resulting Set at dstkey </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SunionCommand.html">SUNION</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key1</i> <i>key2</i> ... <i>keyN</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return the union between the Sets stored at key1, key2, ..., keyN </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SunionstoreCommand.html">SUNIONSTORE</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>dstkey</i> <i>key1</i> <i>key2</i> ... <i>keyN</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Compute the union between the Sets stored at key1, key2, ..., keyN, and store the resulting Set at dstkey </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SdiffCommand.html">SDIFF</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key1</i> <i>key2</i> ... <i>keyN</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return the difference between the Set stored at key1 and all the Sets key2, ..., keyN </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SdiffstoreCommand.html">SDIFFSTORE</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>dstkey</i> <i>key1</i> <i>key2</i> ... <i>keyN</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Compute the difference between the Set key1 and all the Sets key2, ..., keyN, and store the resulting Set at dstkey </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SmembersCommand.html">SMEMBERS</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return all the members of the Set value at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SrandmemberCommand.html">SRANDMEMBER</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return a random member of the Set value at key </td></tr></table>
+<h2><a name="Commands operating on sorted zsets (sorted sets)">Commands operating on sorted zsets (sorted sets)</a></h2><table><tr><td style="border: 1px solid #aaa; padding: 5px;"> <b>Command</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Parameters</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Description</b> </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ZaddCommand.html">ZADD</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>score</i> <i>member</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Add the specified member to the Sorted Set value at key or update the score if it already exist </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ZremCommand.html">ZREM</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>member</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Remove the specified member from the Sorted Set value at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ZincrbyCommand.html">ZINCRBY</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>increment</i> <i>member</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> If the member already exists increment its score by <i>increment</i>, otherwise add the member setting <i>increment</i> as score </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ZrankCommand.html">ZRANK</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>member</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return the rank (or index) or <i>member</i> in the sorted set at <i>key</i>, with scores being ordered from low to high </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ZrankCommand.html">ZREVRANK</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>member</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return the rank (or index) or <i>member</i> in the sorted set at <i>key</i>, with scores being ordered from high to low </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ZrangeCommand.html">ZRANGE</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>start</i> <i>end</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return a range of elements from the sorted set at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ZrangeCommand.html">ZREVRANGE</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>start</i> <i>end</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return a range of elements from the sorted set at key, exactly like ZRANGE, but the sorted set is ordered in traversed in reverse order, from the greatest to the smallest score </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ZrangebyscoreCommand.html">ZRANGEBYSCORE</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>min</i> <i>max</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return all the elements with score >= min and score <= max (a range query) from the sorted set </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ZrangebyscoreCommand.html">ZCOUNT</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>min</i> <i>max</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return the number of elements with score >= min and score <= max in the sorted set </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ZcardCommand.html">ZCARD</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return the cardinality (number of elements) of the sorted set at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ZscoreCommand.html">ZSCORE</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>element</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return the score associated with the specified element of the sorted set at key </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ZremrangebyrankCommand.html">ZREMRANGEBYRANK</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>min</i> <i>max</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Remove all the elements with rank >= min and rank <= max from the sorted set </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ZremrangebyscoreCommand.html">ZREMRANGEBYSCORE</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>min</i> <i>max</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Remove all the elements with score >= min and score <= max from the sorted set </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ZunionstoreCommand.html">ZUNIONSTORE / ZINTERSTORE</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>dstkey</i> <i>N</i> <i>key1</i> ... <i>keyN</i> WEIGHTS <i>w1</i> ... <i>wN</i> AGGREGATE SUM|MIN|MAX </td><td style="border: 1px solid #aaa; padding: 5px;"> Perform a union or intersection over a number of sorted sets with optional weight and aggregate </td></tr></table>
+<h2><a name="Commands operating on hashes">Commands operating on hashes</a></h2><table><tr><td style="border: 1px solid #aaa; padding: 5px;"> <b>Command</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Parameters</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Description</b> </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="HsetCommand.html">HSET</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>field</i> <i>value</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Set the hash field to the specified value. Creates the hash if needed. </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="HgetCommand.html">HGET</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>field</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Retrieve the value of the specified hash field. </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="HmgetCommand.html">HMGET</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>field1</i> ... <i>fieldN</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Get the hash values associated to the specified fields. </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="HmsetCommand.html">HMSET</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>field1</i> <i>value1</i> ... <i>fieldN</i> <i>valueN</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Set the hash fields to their respective values. </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="HincrbyCommand.html">HINCRBY</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>field</i> <i>integer</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Increment the integer value of the hash at <i>key</i> on <i>field</i> with <i>integer</i>. </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="HexistsCommand.html">HEXISTS</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>field</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Test for existence of a specified field in a hash </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="HdelCommand.html">HDEL</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> <i>field</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Remove the specified field from a hash </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="HlenCommand.html">HLEN</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return the number of items in a hash. </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="HgetallCommand.html">HKEYS</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return all the fields in a hash. </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="HgetallCommand.html">HVALS</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return all the values in a hash. </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="HgetallCommand.html">HGETALL</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> </td><td style="border: 1px solid #aaa; padding: 5px;"> Return all the fields and associated values in a hash. </td></tr></table>
+<h2><a name="Sorting">Sorting</a></h2><table><tr><td style="border: 1px solid #aaa; padding: 5px;"> <b>Command</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Parameters</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Description</b> </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SortCommand.html">SORT</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> <i>key</i> BY <i>pattern</i> LIMIT <i>start</i> <i>end</i> GET <i>pattern</i> ASC|DESC ALPHA </td><td style="border: 1px solid #aaa; padding: 5px;"> Sort a Set or a List accordingly to the specified parameters </td></tr></table>
+<h2><a name="Transactions">Transactions</a></h2><table><tr><td style="border: 1px solid #aaa; padding: 5px;"> <b>Command</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Parameters</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Description</b> </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="MultiExecCommand.html">MULTI/EXEC/DISCARD/WATCH/UNWATCH</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> - </td><td style="border: 1px solid #aaa; padding: 5px;"> Redis atomic transactions </td></tr></table>
+<h2><a name="Publish/Subscribe">Publish/Subscribe</a></h2><table><tr><td style="border: 1px solid #aaa; padding: 5px;"> <b>Command</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Parameters</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Description</b> </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="PublishSubscribe.html">SUBSCRIBE/UNSUBSCRIBE/PUBLISH</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> - </td><td style="border: 1px solid #aaa; padding: 5px;"> Redis Public/Subscribe messaging paradigm implementation </td></tr></table>
+<h2><a name="Persistence control commands">Persistence control commands</a></h2><table><tr><td style="border: 1px solid #aaa; padding: 5px;"> <b>Command</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Parameters</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Description</b> </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SaveCommand.html">SAVE</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> - </td><td style="border: 1px solid #aaa; padding: 5px;"> Synchronously save the DB on disk </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="BgsaveCommand.html">BGSAVE</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> - </td><td style="border: 1px solid #aaa; padding: 5px;"> Asynchronously save the DB on disk </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="LastsaveCommand.html">LASTSAVE</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> - </td><td style="border: 1px solid #aaa; padding: 5px;"> Return the UNIX time stamp of the last successfully saving of the dataset on disk </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ShutdownCommand.html">SHUTDOWN</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> - </td><td style="border: 1px solid #aaa; padding: 5px;"> Synchronously save the DB on disk, then shutdown the server </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="BgrewriteaofCommand.html">BGREWRITEAOF</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> - </td><td style="border: 1px solid #aaa; padding: 5px;"> Rewrite the append only file in background when it gets too big </td></tr></table>
+<h2><a name="Remote server control commands">Remote server control commands</a></h2><table><tr><td style="border: 1px solid #aaa; padding: 5px;"> <b>Command</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Parameters</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Description</b> </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="InfoCommand.html">INFO</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> - </td><td style="border: 1px solid #aaa; padding: 5px;"> Provide information and statistics about the server </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="MonitorCommand.html">MONITOR</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> - </td><td style="border: 1px solid #aaa; padding: 5px;"> Dump all the received requests in real time </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="SlaveofCommand.html">SLAVEOF</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> - </td><td style="border: 1px solid #aaa; padding: 5px;"> Change the replication settings </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> <a href="ConfigCommand.html">CONFIG</a> </td><td style="border: 1px solid #aaa; padding: 5px;"> - </td><td style="border: 1px solid #aaa; padding: 5px;"> Configure a Redis server at runtime </td></tr></table>
</div>
</div>
<div class="narrow">
#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="DEL _key1_ _key2_ ... _keyN_">DEL _key1_ _key2_ ... _keyN_</a></h1>
-<i>Time complexity: O(1)</i><blockquote>Remove the specified keys. If a given key does not existno operation is performed for this key. The commnad returns the number ofkeys removed.</blockquote>
+<i>Time complexity: O(1)</i><blockquote>Remove the specified keys. If a given key does not existno operation is performed for this key. The command returns the number ofkeys removed.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
an integer greater than 0 if one or more keys were removed
0 if none of the specified key existed
</pre>
-
</div>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
-<b>ExpireCommand: Contents</b><br> <a href="#EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a><br> <a href="#EXPIREAT _key_ _unixtime_ (Redis >">EXPIREAT _key_ _unixtime_ (Redis ></a><br> <a href="#How the expire is removed from a key">How the expire is removed from a key</a><br> <a href="#Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a><br> <a href="#Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a><br> <a href="#Enhanced Lazy Expiration algorithm">Enhanced Lazy Expiration algorithm</a><br> <a href="#Version 1.0">Version 1.0</a><br> <a href="#Version 1.1">Version 1.1</a><br> <a href="#Return value">Return value</a><br> <a href="#FAQ: Can you explain better why Redis deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis deletes keys with an EXPIRE on write operations?</a>
+<b>ExpireCommand: Contents</b><br> <a href="#EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a><br> <a href="#EXPIREAT _key_ _unixtime_ (Redis >">EXPIREAT _key_ _unixtime_ (Redis ></a><br> <a href="#PERSIST _key_">PERSIST _key_</a><br> <a href="#How the expire is removed from a key">How the expire is removed from a key</a><br> <a href="#Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a><br> <a href="#Restrictions for write operations with volatile keys as sources">Restrictions for write operations with volatile keys as sources</a><br> <a href="#Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a><br> <a href="#Enhanced Lazy Expiration algorithm">Enhanced Lazy Expiration algorithm</a><br> <a href="#Version 1.0">Version 1.0</a><br> <a href="#Version 1.1">Version 1.1</a><br> <a href="#Return value">Return value</a><br> <a href="#FAQ: Can you explain better why Redis < 2.1.3 deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis < 2.1.3 deletes keys with an EXPIRE on write operations?</a><br> <a href="#FAQ: How this limitations were solved in Redis versions > 2.1.3?">FAQ: How this limitations were solved in Redis versions > 2.1.3?</a>
</div>
<h1 class="wikiname">ExpireCommand</h1>
<div class="narrow">
#sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="EXPIRE _key_ _seconds_">EXPIRE _key_ _seconds_</a></h1>
<h1><a name="EXPIREAT _key_ _unixtime_ (Redis >">EXPIREAT _key_ _unixtime_ (Redis ></a></h1> 1.1)=
+<h1><a name="PERSIST _key_">PERSIST _key_</a></h1>
<i>Time complexity: O(1)</i><blockquote>Set a timeout on the specified key. After the timeout the key will beautomatically delete by the server. A key with an associated timeout issaid to be <i>volatile</i> in Redis terminology.</blockquote>
-<blockquote>Voltile keys are stored on disk like the other keys, the timeout is persistenttoo like all the other aspects of the dataset. Saving a dataset containingthe dataset and stopping the server does not stop the flow of time as Redisregisters on disk when the key will no longer be available as Unix time, andnot the remaining seconds.</blockquote>
+<blockquote>Voltile keys are stored on disk like the other keys, the timeout is persistenttoo like all the other aspects of the dataset. Saving a dataset containingexpires and stopping the server does not stop the flow of time as Redisstores on disk the time when the key will no longer be available as Unixtime, and not the remaining seconds.</blockquote>
<blockquote>EXPIREAT works exctly like EXPIRE but instead to get the number of secondsrepresenting the Time To Live of the key as a second argument (that is arelative way of specifing the TTL), it takes an absolute one in the form ofa UNIX timestamp (Number of seconds elapsed since 1 Gen 1970).</blockquote>
-<blockquote>EXPIREAT was introduced in order to implement [Persistence append only saving mode] so that EXPIRE commands are automatically translated into EXPIREAT commands for the append only file. Of course EXPIREAT can alsoused by programmers that need a way to simply specify that a given key should expire at a given time in the future.</blockquote>
-<h2><a name="How the expire is removed from a key">How the expire is removed from a key</a></h2><blockquote>When the key is set to a new value using the SET command, the INCR commandor any other command that modify the value stored at key the timeout isremoved from the key and the key becomes non volatile.</blockquote>
-<h2><a name="Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a></h2><blockquote>Write operations like LPUSH, LSET and every other command that has theeffect of modifying the value stored at a volatile key have a special semantic:basically a volatile key is destroyed when it is target of a write operation.See for example the following usage pattern:</blockquote>
+<blockquote>EXPIREAT was introduced in order to implement <a href="AppendOnlyFileHowto.html">the Append Only File persistence mode</a>so that EXPIRE commands are automatically translated into EXPIREAT commands for the append only file. Of course EXPIREAT can alsoused by programmers that need a way to simply specify that a given key should expire at a given time in the future.</blockquote>
+<blockquote>Since Redis 2.1.3 you can update the value of the timeout of a key alreadyhaving an expire set. It is also possible to undo the expire at allturning the key into a normal key using the PERSIST command.</blockquote>
+<h2><a name="How the expire is removed from a key">How the expire is removed from a key</a></h2><blockquote>When the key is set to a new value using the SET command, or when a keyis destroied via DEL, the timeout is removed from the key.</blockquote>
+<h2><a name="Restrictions with write operations against volatile keys">Restrictions with write operations against volatile keys</a></h2><blockquote>IMPORTANT: Since Redis 2.1.3 or greater, there are no restrictions aboutthe operations you can perform against volatile keys, however older versionsof Redis, including the current stable version 2.0.0, has the followinglimitations:</blockquote>
+<blockquote>Write operations like LPUSH, LSET and every other command that has theeffect of modifying the value stored at a volatile key have a special semantic:basically a volatile key is destroyed when it is target of a write operation.See for example the following usage pattern:</blockquote>
<pre class="codeblock python" name="code">
% ./redis-cli lpush mylist foobar /Users/antirez/hack/redis
OK
OK
% ./redis-cli lrange mylist 0 -1 /Users/antirez/hack/redis
1. newelement
-</pre><blockquote>What happened here is that lpush against the key with a timeout set deletedthe key before to perform the operation. There is so a simple rule, writeoperations against volatile keys will destroy the key before to perform theoperation. Why Redis uses this behavior? In order to retain an importantproperty: a server that receives a given number of commands in the samesequence will end with the same dataset in memory. Without the delete-on-writesemantic what happens is that the state of the server depends on the timeof the commands to. This is not a desirable property in a distributed databasethat supports replication.</blockquote>
-<h2><a name="Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a></h2><blockquote>Trying to call EXPIRE against a key that already has an associated timeoutwill not change the timeout of the key, but will just return 0. If insteadthe key does not have a timeout associated the timeout will be set and EXPIREwill return 1.</blockquote>
+</pre><blockquote>What happened here is that LPUSH against the key with a timeout set deletedthe key before to perform the operation. There is so a simple rule, writeoperations against volatile keys will destroy the key before to perform theoperation. Why Redis uses this behavior? In order to retain an importantproperty: a server that receives a given number of commands in the samesequence will end with the same dataset in memory. Without the delete-on-writesemantic what happens is that the state of the server depends on the timethe commands were issued. This is not a desirable property in a distributed databasethat supports replication.</blockquote>
+<h2><a name="Restrictions for write operations with volatile keys as sources">Restrictions for write operations with volatile keys as sources</a></h2>Even when the volatile key is not modified as part of a write operation, if it is
+read in a composite write operation (such as SINTERSTORE) it will be cleared at the
+start of the operation. This is done to avoid concurrency issues in replication.
+Imagine a key that is about to expire and the composite operation is run against it.
+On a slave node, this key might already be expired, which leaves you with a
+desync in your dataset.<h2><a name="Setting the timeout again on already volatile keys">Setting the timeout again on already volatile keys</a></h2><blockquote>Trying to call EXPIRE against a key that already has an associated timeoutwill not change the timeout of the key, but will just return 0. If insteadthe key does not have a timeout associated the timeout will be set and EXPIREwill return 1.</blockquote>
<h2><a name="Enhanced Lazy Expiration algorithm">Enhanced Lazy Expiration algorithm</a></h2><blockquote>Redis does not constantly monitor keys that are going to be expired.Keys are expired simply when some client tries to access a key, andthe key is found to be timed out.</blockquote>
<blockquote>Of course this is not enough as there are expired keys that will neverbe accessed again. This keys should be expired anyway, so once everysecond Redis test a few keys at random among keys with an expire set.All the keys that are already expired are deleted from the keyspace. </blockquote>
<h3><a name="Version 1.0">Version 1.0</a></h3><blockquote>Each time a fixed number of keys where tested (100 by default). So ifyou had a client setting keys with a very short expire faster than 100for second the memory continued to grow. When you stopped to insertnew keys the memory started to be freed, 100 keys every second in thebest conditions. Under a peak Redis continues to use more and more RAMeven if most keys are expired in each sweep.</blockquote>
<blockquote>This means that at any given moment the maximum amount of keys alreadyexpired that are using memory is at max equal to max setting operations per second divided by 4.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python python" name="code">
1: the timeout was set.
-0: the timeout was not set since the key already has an associated timeout, or the key does not exist.
-</pre><h2><a name="FAQ: Can you explain better why Redis deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis deletes keys with an EXPIRE on write operations?</a></h2>
+0: the timeout was not set since the key already has an associated timeout
+ (this may happen only in Redis versions < 2.1.3, Redis >= 2.1.3 will
+ happily update the timeout), or the key does not exist.
+</pre><h2><a name="FAQ: Can you explain better why Redis < 2.1.3 deletes keys with an EXPIRE on write operations?">FAQ: Can you explain better why Redis < 2.1.3 deletes keys with an EXPIRE on write operations?</a></h2>
Ok let's start with the problem:
<pre class="codeblock python python python" name="code">
redis> set a 100
INCR a
</pre>
Imagine a Redis version that does not implement the "Delete keys with an expire set on write operation" semantic.
-Running the above example with the 10 seconds pause will lead to 'a' being set to the value of 1, as it no longer exists when INCR is called 10 seconds later.<br/><br/>Instead if we drop the 10 seconds pause, the result is that 'a' is set to 101.<br/><br/>And in the practice timing changes! For instance the client may wait 10 seconds before INCR, but the sequence written in the Append Only File (and later replayed-back as fast as possible when Redis is restarted) will not have the pause. Even if we add a timestamp in the AOF, when the time difference is smaller than our timer resolution, we have a race condition.<br/><br/>The same happens with master-slave replication. Again, consider the example above: the client will use the same sequence of commands without the 10 seconds pause, but the replication link will slow down for a few seconds due to a network problem. Result? The master will contain 'a' set to 101, the slave 'a' set to 1.<br/><br/>The only way to avoid this but at the same time have reliable non time dependent timeouts on keys is to destroy volatile keys when a write operation is attempted against it.<br/><br/>After all Redis is one of the rare fully persistent databases that will give you EXPIRE. This comes to a cost :)
+Running the above example with the 10 seconds pause will lead to 'a' being set to the value of 1, as it no longer exists when INCR is called 10 seconds later.<br/><br/>Instead if we drop the 10 seconds pause, the result is that 'a' is set to 101.<br/><br/>And in the practice timing changes! For instance the client may wait 10 seconds before INCR, but the sequence written in the Append Only File (and later replayed-back as fast as possible when Redis is restarted) will not have the pause. Even if we add a timestamp in the AOF, when the time difference is smaller than our timer resolution, we have a race condition.<br/><br/>The same happens with master-slave replication. Again, consider the example above: the client will use the same sequence of commands without the 10 seconds pause, but the replication link will slow down for a few seconds due to a network problem. Result? The master will contain 'a' set to 101, the slave 'a' set to 1.<br/><br/>The only way to avoid this but at the same time have reliable non time dependent timeouts on keys is to destroy volatile keys when a write operation is attempted against it.<br/><br/>After all Redis is one of the rare fully persistent databases that will give you EXPIRE. This comes to a cost :)<h2><a name="FAQ: How this limitations were solved in Redis versions > 2.1.3?">FAQ: How this limitations were solved in Redis versions > 2.1.3?</a></h2>Since Redis 2.1.3 there are no longer restrictions in the use you can do of write commands against volatile keys, still the replication and AOF file are guaranteed to be fully consistent.<br/><br/>In order to obtain a correct behavior without sacrificing consistency now when a key expires, a DEL operation is synthesized in both the AOF file and against all the attached slaves. This way the expiration process is centralized in the master instance, and there is no longer a chance of consistency errors.<br/><br/>However while the slaves while connected to a master will not expire keys independently, they'll still take the full state of the expires existing in the dataset, so when a slave is elected to a master it will be able to expire the keys independently, fully acting as a master.
+
</div>
</div>
memory is full of pointers, reference counters and other metadata. Add
to this malloc fragmentation and need to return word-aligned chunks of
memory and you have a clear picture of what happens. So this means to
-have 10 times the I/O between memory and disk than otherwise needed.<h1><a name="Is there something I can do to lower the Redis memory usage?">Is there something I can do to lower the Redis memory usage?</a></h1>Yes, try to compile it with 32 bit target if you are using a 64 bit box.<br/><br/>If you are using Redis >= 1.3, try using the Hash data type, it can save a lot of memory.<br/><br/>If you are using hashes or any other type with values bigger than 128 bytes try also this to lower the RSS usage (Resident Set Size): EXPORT MMAP_THRESHOLD=4096<h1><a name="I have an empty Redis server but INFO and logs are reporting megabytes of memory in use!">I have an empty Redis server but INFO and logs are reporting megabytes of memory in use!</a></h1>This may happen and it's prefectly ok. Redis objects are small C structures allocated and freed a lot of times. This costs a lot of CPU so instead of being freed, released objects are taken into a free list and reused when needed. This memory is taken exactly by this free objects ready to be reused.<h1><a name="What happens if Redis runs out of memory?">What happens if Redis runs out of memory?</a></h1>With modern operating systems malloc() returning NULL is not common, usually the server will start swapping and Redis performances will be disastrous so you'll know it's time to use more Redis servers or get more RAM.<br/><br/>The INFO command (work in progress in this days) will report the amount of memory Redis is using so you can write scripts that monitor your Redis servers checking for critical conditions.<br/><br/>You can also use the "maxmemory" option in the config file to put a limit to the memory Redis can use. If this limit is reached Redis will start to reply with an error to write commands (but will continue to accept read-only commands).<h1><a name="Does Redis use more memory running in 64 bit boxes? Can I use 32 bit Redis in 64 bit systems?">Does Redis use more memory running in 64 bit boxes? Can I use 32 bit Redis in 64 bit systems?</a></h1>Redis uses a lot more memory when compiled for 64 bit target, especially if the dataset is composed of many small keys and values. Such a database will, for instance, consume 50 MB of RAM when compiled for the 32 bit target, and 80 MB for 64 bit! That's a big difference.<br/><br/>You can run 32 bit Redis binaries in a 64 bit Linux and Mac OS X system without problems. For OS X just use <b>make 32bit</b>. For Linux instead, make sure you have <b>libc6-dev-i386</b> installed, then use <b>make 32bit</b> if you are using the latest Git version. Instead for Redis <= 1.2.2 you have to edit the Makefile and replace "-arch i386" with "-m32".<br/><br/>If your application is already able to perform application-level sharding, it is very advisable to run N instances of Redis 32bit against a big 64 bit Redis box (with more than 4GB of RAM) instead than a single 64 bit instance, as this is much more memory efficient. <h1><a name="How much time it takes to load a big database at server startup?">How much time it takes to load a big database at server startup?</a></h1>Just an example on normal hardware: It takes about 45 seconds to restore a 2 GB database on a fairly standard system, no RAID. This can give you some kind of feeling about the order of magnitude of the time needed to load data when you restart the server.<h1><a name="Background saving is failing with a fork() error under Linux even if I've a lot of free RAM!">Background saving is failing with a fork() error under Linux even if I've a lot of free RAM!</a></h1>Short answer: <code name="code" class="python">echo 1 > /proc/sys/vm/overcommit_memory</code> :)<br/><br/>And now the long one:<br/><br/>Redis background saving schema relies on the copy-on-write semantic of fork in modern operating systems: Redis forks (creates a child process) that is an exact copy of the parent. The child process dumps the DB on disk and finally exits. In theory the child should use as much memory as the parent being a copy, but actually thanks to the copy-on-write semantic implemented by most modern operating systems the parent and child process will <i>share</i> the common memory pages. A page will be duplicated only when it changes in the child or in the parent. Since in theory all the pages may change while the child process is saving, Linux can't tell in advance how much memory the child will take, so if the <code name="code" class="python">overcommit_memory</code> setting is set to zero fork will fail unless there is as much free RAM as required to really duplicate all the parent memory pages, with the result that if you have a Redis dataset of 3 GB and just 2 GB of free memory it will fail.<br/><br/>Setting <code name="code" class="python">overcommit_memory</code> to 1 says Linux to relax and perform the fork in a more optimistic allocation fashion, and this is indeed what you want for Redis.<h1><a name="Are Redis on disk snapshots atomic?">Are Redis on disk snapshots atomic?</a></h1>Yes, redis background saving process is always fork(2)ed when the server is outside of the execution of a command, so every command reported to be atomic in RAM is also atomic from the point of view of the disk snapshot.<h1><a name="Redis is single threaded, how can I exploit multiple CPU / cores?">Redis is single threaded, how can I exploit multiple CPU / cores?</a></h1>Simply start multiple instances of Redis in different ports in the same box and threat them as different servers! Given that Redis is a distributed database anyway in order to scale you need to think in terms of multiple computational units. At some point a single box may not be enough anyway.<br/><br/>In general key-value databases are very scalable because of the property that different keys can stay on different servers independently.<br/><br/>In Redis there are client libraries such Redis-rb (the Ruby client) that are able to handle multiple servers automatically using <i>consistent hashing</i>. We are going to implement consistent hashing in all the other major client libraries. If you use a different language you can implement it yourself otherwise just hash the key before to SET / GET it from a given server. For example imagine to have N Redis servers, server-0, server-1, ..., server-N. You want to store the key "foo", what's the right server where to put "foo" in order to distribute keys evenly among different servers? Just perform the <i>crc</i> = CRC32("foo"), then <i>servernum</i> = <i>crc</i> % N (the rest of the division for N). This will give a number between 0 and N-1 for every key. Connect to this server and store the key. The same for gets.<br/><br/>This is a basic way of performing key partitioning, consistent hashing is much better and this is why after Redis 1.0 will be released we'll try to implement this in every widely used client library starting from Python and PHP (Ruby already implements this support).<h1><a name="I'm using some form of key hashing for partitioning, but what about SORT BY?">I'm using some form of key hashing for partitioning, but what about SORT BY?</a></h1>With <a href="SortCommand.html">SORT</a> BY you need that all the <i>weight keys</i> are in the same Redis instance of the list/set you are trying to sort. In order to make this possible we developed a concept called <i>key tags</i>. A key tag is a special pattern inside a key that, if preset, is the only part of the key hashed in order to select the server for this key. For example in order to hash the key "foo" I simply perform the CRC32 checksum of the whole string, but if this key has a pattern in the form of the characters {...} I only hash this substring. So for example for the key "foo{bared}" the key hashing code will simply perform the CRC32 of "bared". This way using key tags you can ensure that related keys will be stored on the same Redis instance just using the same key tag for all this keys. Redis-rb already implements key tags.<h1><a name="What is the maximum number of keys a single Redis instance can hold? and what the max number of elements in a List, Set, Ordered Set?">What is the maximum number of keys a single Redis instance can hold? and what the max number of elements in a List, Set, Ordered Set?</a></h1>In theory Redis can handle up to 2<sup>32 keys, and was tested in practice to handle at least 150 million of keys per instance. We are working in order to experiment with larger values.<br/><br/>Every list, set, and ordered set, can hold 2</sup>32 elements.<br/><br/>Actually Redis internals are ready to allow up to 2<sup>64 elements but the current disk dump format don't support this, and there is a lot time to fix this issues in the future as currently even with 128 GB of RAM it's impossible to reach 2</sup>32 elements.<h1><a name="What Redis means actually?">What Redis means actually?</a></h1>Redis means two things:
+have 10 times the I/O between memory and disk than otherwise needed.<h1><a name="Is there something I can do to lower the Redis memory usage?">Is there something I can do to lower the Redis memory usage?</a></h1>Yes, try to compile it with 32 bit target if you are using a 64 bit box.<br/><br/>If you are using Redis >= 1.3, try using the Hash data type, it can save a lot of memory.<br/><br/>If you are using hashes or any other type with values bigger than 128 bytes try also this to lower the RSS usage (Resident Set Size): EXPORT MMAP_THRESHOLD=4096<h1><a name="I have an empty Redis server but INFO and logs are reporting megabytes of memory in use!">I have an empty Redis server but INFO and logs are reporting megabytes of memory in use!</a></h1>This may happen and it's prefectly ok. Redis objects are small C structures allocated and freed a lot of times. This costs a lot of CPU so instead of being freed, released objects are taken into a free list and reused when needed. This memory is taken exactly by this free objects ready to be reused.<h1><a name="What happens if Redis runs out of memory?">What happens if Redis runs out of memory?</a></h1>With modern operating systems malloc() returning NULL is not common, usually the server will start swapping and Redis performances will be disastrous so you'll know it's time to use more Redis servers or get more RAM.<br/><br/>The INFO command (work in progress in this days) will report the amount of memory Redis is using so you can write scripts that monitor your Redis servers checking for critical conditions.<br/><br/>You can also use the "maxmemory" option in the config file to put a limit to the memory Redis can use. If this limit is reached Redis will start to reply with an error to write commands (but will continue to accept read-only commands).<h1><a name="Does Redis use more memory running in 64 bit boxes? Can I use 32 bit Redis in 64 bit systems?">Does Redis use more memory running in 64 bit boxes? Can I use 32 bit Redis in 64 bit systems?</a></h1>Redis uses a lot more memory when compiled for 64 bit target, especially if the dataset is composed of many small keys and values. Such a database will, for instance, consume 50 MB of RAM when compiled for the 32 bit target, and 80 MB for 64 bit! That's a big difference.<br/><br/>You can run 32 bit Redis binaries in a 64 bit Linux and Mac OS X system without problems. For OS X just use <b>make 32bit</b>. For Linux instead, make sure you have <b>libc6-dev-i386</b> installed, then use <b>make 32bit</b> if you are using the latest Git version. Instead for Redis <= 1.2.2 you have to edit the Makefile and replace "-arch i386" with "-m32".<br/><br/>If your application is already able to perform application-level sharding, it is very advisable to run N instances of Redis 32bit against a big 64 bit Redis box (with more than 4GB of RAM) instead than a single 64 bit instance, as this is much more memory efficient. <h1><a name="How much time it takes to load a big database at server startup?">How much time it takes to load a big database at server startup?</a></h1>Just an example on normal hardware: It takes about 45 seconds to restore a 2 GB database on a fairly standard system, no RAID. This can give you some kind of feeling about the order of magnitude of the time needed to load data when you restart the server.<h1><a name="Background saving is failing with a fork() error under Linux even if I've a lot of free RAM!">Background saving is failing with a fork() error under Linux even if I've a lot of free RAM!</a></h1>Short answer: <code name="code" class="python">echo 1 > /proc/sys/vm/overcommit_memory</code> :)<br/><br/>And now the long one:<br/><br/>Redis background saving schema relies on the copy-on-write semantic of fork in modern operating systems: Redis forks (creates a child process) that is an exact copy of the parent. The child process dumps the DB on disk and finally exits. In theory the child should use as much memory as the parent being a copy, but actually thanks to the copy-on-write semantic implemented by most modern operating systems the parent and child process will <i>share</i> the common memory pages. A page will be duplicated only when it changes in the child or in the parent. Since in theory all the pages may change while the child process is saving, Linux can't tell in advance how much memory the child will take, so if the <code name="code" class="python">overcommit_memory</code> setting is set to zero fork will fail unless there is as much free RAM as required to really duplicate all the parent memory pages, with the result that if you have a Redis dataset of 3 GB and just 2 GB of free memory it will fail.<br/><br/>Setting <code name="code" class="python">overcommit_memory</code> to 1 says Linux to relax and perform the fork in a more optimistic allocation fashion, and this is indeed what you want for Redis.<br/><br/>A good source to understand how Linux Virtual Memory work and other alternatives for <code name="code" class="python">overcommit_memory</code> and <code name="code" class="python">overcommit_ratio</code> is this classic from Red Hat Magaize, "Understanding Virtual Memory": <a href="http://www.redhat.com/magazine/001nov04/features/vm/" target="_blank">http://www.redhat.com/magazine/001nov04/features/vm/</a> <h1><a name="Are Redis on disk snapshots atomic?">Are Redis on disk snapshots atomic?</a></h1>Yes, redis background saving process is always fork(2)ed when the server is outside of the execution of a command, so every command reported to be atomic in RAM is also atomic from the point of view of the disk snapshot.<h1><a name="Redis is single threaded, how can I exploit multiple CPU / cores?">Redis is single threaded, how can I exploit multiple CPU / cores?</a></h1>Simply start multiple instances of Redis in different ports in the same box and threat them as different servers! Given that Redis is a distributed database anyway in order to scale you need to think in terms of multiple computational units. At some point a single box may not be enough anyway.<br/><br/>In general key-value databases are very scalable because of the property that different keys can stay on different servers independently.<br/><br/>In Redis there are client libraries such Redis-rb (the Ruby client) that are able to handle multiple servers automatically using <i>consistent hashing</i>. We are going to implement consistent hashing in all the other major client libraries. If you use a different language you can implement it yourself otherwise just hash the key before to SET / GET it from a given server. For example imagine to have N Redis servers, server-0, server-1, ..., server-N. You want to store the key "foo", what's the right server where to put "foo" in order to distribute keys evenly among different servers? Just perform the <i>crc</i> = CRC32("foo"), then <i>servernum</i> = <i>crc</i> % N (the rest of the division for N). This will give a number between 0 and N-1 for every key. Connect to this server and store the key. The same for gets.<br/><br/>This is a basic way of performing key partitioning, consistent hashing is much better and this is why after Redis 1.0 will be released we'll try to implement this in every widely used client library starting from Python and PHP (Ruby already implements this support).<h1><a name="I'm using some form of key hashing for partitioning, but what about SORT BY?">I'm using some form of key hashing for partitioning, but what about SORT BY?</a></h1>With <a href="SortCommand.html">SORT</a> BY you need that all the <i>weight keys</i> are in the same Redis instance of the list/set you are trying to sort. In order to make this possible we developed a concept called <i>key tags</i>. A key tag is a special pattern inside a key that, if preset, is the only part of the key hashed in order to select the server for this key. For example in order to hash the key "foo" I simply perform the CRC32 checksum of the whole string, but if this key has a pattern in the form of the characters {...} I only hash this substring. So for example for the key "foo{bared}" the key hashing code will simply perform the CRC32 of "bared". This way using key tags you can ensure that related keys will be stored on the same Redis instance just using the same key tag for all this keys. Redis-rb already implements key tags.<h1><a name="What is the maximum number of keys a single Redis instance can hold? and what the max number of elements in a List, Set, Ordered Set?">What is the maximum number of keys a single Redis instance can hold? and what the max number of elements in a List, Set, Ordered Set?</a></h1>In theory Redis can handle up to 2<sup>32 keys, and was tested in practice to handle at least 150 million of keys per instance. We are working in order to experiment with larger values.<br/><br/>Every list, set, and ordered set, can hold 2</sup>32 elements.<br/><br/>Actually Redis internals are ready to allow up to 2<sup>64 elements but the current disk dump format don't support this, and there is a lot time to fix this issues in the future as currently even with 128 GB of RAM it's impossible to reach 2</sup>32 elements.<h1><a name="What Redis means actually?">What Redis means actually?</a></h1>Redis means two things:
<ul><li> it's a joke on the word Redistribute (instead to use just a Relational DB redistribute your workload among Redis servers)</li><li> it means REmote DIctionary Server</li></ul>
<h1><a name="Why did you started the Redis project?">Why did you started the Redis project?</a></h1>In order to scale <a href="http://lloogg.com" target="_blank">LLOOGG</a>. But after I got the basic server working I liked the idea to share the work with other guys, and Redis was turned into an open source project.
</div>
</div>
<div class="narrow">
- == Generic Commands ==<br/><br/><ul><li> <a href="ExistsCommand.html">EXISTS</a></li><li> <a href="DelCommand.html">DEL</a></li><li> <a href="TypeCommand.html">TYPE</a></li><li> <a href="KeysCommand.html">KEYS</a></li><li> <a href="RandomkeyCommand.html">RANDOMKEY</a></li><li> <a href="RenameCommand.html">RENAME</a></li><li> <a href="RenamenxCommand.html">RENAMENX</a></li><li> <a href="DbsizeCommand.html">DBSIZE</a></li><li> <a href="ExpireCommand.html">EXPIRE</a></li><li> <a href="TtlCommand.html">TTL</a></li><li> <a href="SelectCommand.html">SELECT</a></li><li> <a href="MoveCommand.html">MOVE</a></li><li> <a href="FlushdbCommand.html">FLUSHDB</a></li><li> <a href="FlushallCommand.html">FLUSHALL</a></li></ul>
+ == Generic Commands ==<br/><br/><ul><li> <a href="ExistsCommand.html">EXISTS</a></li><li> <a href="DelCommand.html">DEL</a></li><li> <a href="TypeCommand.html">TYPE</a></li><li> <a href="KeysCommand.html">KEYS</a></li><li> <a href="RandomkeyCommand.html">RANDOMKEY</a></li><li> <a href="RenameCommand.html">RENAME</a></li><li> <a href="RenamenxCommand.html">RENAMENX</a></li><li> <a href="DbsizeCommand.html">DBSIZE</a></li><li> <a href="ExpireCommand.html">EXPIRE</a></li><li> <a href="ExpireCommand.html">PERSIST</a></li><li> <a href="TtlCommand.html">TTL</a></li><li> <a href="SelectCommand.html">SELECT</a></li><li> <a href="MoveCommand.html">MOVE</a></li><li> <a href="FlushdbCommand.html">FLUSHDB</a></li><li> <a href="FlushallCommand.html">FLUSHALL</a></li><li> <a href="MultiExecCommand.html">Redis Transactions</a></li></ul>
</div>
</div>
</div>
<div class="narrow">
- == Hash Commands ==<br/><br/><ul><li> <a href="HsetCommand.html">HSET</a></li><li> <a href="HgetCommand.html">HGET</a></li><li> <a href="HsetnxCommand.html">HSETNX</a></li><li> <a href="HmsetCommand.html">HMSET</a></li><li> <a href="HincrbyCommand.html">HINCRBY</a></li><li> <a href="HexistsCommand.html">HEXISTS</a></li><li> <a href="HdelCommand.html">HDEL</a></li><li> <a href="HlenCommand.html">HLEN</a></li><li> <a href="HgetallCommand.html">HKEYS</a></li><li> <a href="HgetallCommand.html">HVALS</a></li><li> <a href="HgetallCommand.html">HGETALL</a></li></ul>
+ == Hash Commands ==<br/><br/><ul><li> <a href="HsetCommand.html">HSET</a></li><li> <a href="HgetCommand.html">HGET</a></li><li> <a href="HsetnxCommand.html">HSETNX</a></li><li> <a href="HmsetCommand.html">HMSET</a></li><li> <a href="HmgetCommand.html">HMGET</a></li><li> <a href="HincrbyCommand.html">HINCRBY</a></li><li> <a href="HexistsCommand.html">HEXISTS</a></li><li> <a href="HdelCommand.html">HDEL</a></li><li> <a href="HlenCommand.html">HLEN</a></li><li> <a href="HgetallCommand.html">HKEYS</a></li><li> <a href="HgetallCommand.html">HVALS</a></li><li> <a href="HgetallCommand.html">HGETALL</a></li></ul>
</div>
</div>
<blockquote>the slow commands that may ruin the DB performance if not usedwith care*.</blockquote>
<blockquote>In other words this command is intended only for debugging and *special* operations like creating a script to change the DB schema. Don't use it in your normal code. Use Redis <a href="Sets.html">Sets</a> in order to group together a subset of objects.</blockquote>
Glob style patterns examples:
-<blockquote>* h?llo will match hello hallo hhllo* h*llo will match hllo heeeello* h<code name="code" class="python">[</code>ae<code name="code" class="python">]</code>llo will match hello and hallo, but not hillo</blockquote>Use \ to escape special chars if you want to match them verbatim.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a>, specifically a string in the form of space separated list of keys. Note that most client libraries will return an Array of keys and not a single string with space separated keys (that is, split by " " is performed in the client library usually).</b></blockquote>
+<blockquote>* h?llo will match hello hallo hhllo* h*llo will match hllo heeeello* h<code name="code" class="python">[</code>ae<code name="code" class="python">]</code>llo will match hello and hallo, but not hillo</blockquote>Use \ to escape special chars if you want to match them verbatim.<h2><a name="Return value">Return value</a></h2>
+<a href="ReplyTypes.html">Multi bulk reply</a></b></blockquote>
</div>
</div>
<div class="narrow">
#sidebar <a href="ListCommandsSidebar.html">ListCommandsSidebar</a><h1><a name="LINDEX _key_ _index_">LINDEX _key_ _index_</a></h1>
<i>Time complexity: O(n) (with n being the length of the list)</i><blockquote>Return the specified element of the list stored at the specifiedkey. 0 is the first element, 1 the second and so on. Negative indexesare supported, for example -1 is the last element, -2 the penultimateand so on.</blockquote>
-<blockquote>If the value stored at key is not of list type an error is returned.If the index is out of range an empty string is returned.</blockquote>
+<blockquote>If the value stored at key is not of list type an error is returned.If the index is out of range a 'nil' reply is returned.</blockquote>
<blockquote>Note that even if the average time complexity is O(n) asking forthe first or the last element of the list is O(1).</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a>, specifically the requested element.
-
</div>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
-<b>MultiExecCommand: Contents</b><br> <a href="#MULTI">MULTI</a><br> <a href="#COMMAND_1 ...">COMMAND_1 ...</a><br> <a href="#COMMAND_2 ...">COMMAND_2 ...</a><br> <a href="#COMMAND_N ...">COMMAND_N ...</a><br> <a href="#EXEC or DISCARD">EXEC or DISCARD</a><br> <a href="#Usage">Usage</a><br> <a href="#The DISCARD command">The DISCARD command</a><br> <a href="#Return value">Return value</a>
+<b>MultiExecCommand: Contents</b><br> <a href="#WATCH key1 key2 ... keyN (Redis >">WATCH key1 key2 ... keyN (Redis ></a><br> <a href="#UNWATCH">UNWATCH</a><br> <a href="#MULTI">MULTI</a><br> <a href="#COMMAND_1 ...">COMMAND_1 ...</a><br> <a href="#COMMAND_2 ...">COMMAND_2 ...</a><br> <a href="#COMMAND_N ...">COMMAND_N ...</a><br> <a href="#EXEC or DISCARD">EXEC or DISCARD</a><br> <a href="#Usage">Usage</a><br> <a href="#The DISCARD command">The DISCARD command</a><br> <a href="#Check and Set (CAS) transactions using WATCH">Check and Set (CAS) transactions using WATCH</a><br> <a href="#WATCH explained">WATCH explained</a><br> <a href="#WATCH used to implement ZPOP">WATCH used to implement ZPOP</a><br> <a href="#Return value">Return value</a>
</div>
<h1 class="wikiname">MultiExecCommand</h1>
</div>
<div class="narrow">
- #sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="MULTI">MULTI</a></h1>
+ #sidebar <a href="GenericCommandsSidebar.html">GenericCommandsSidebar</a><h1><a name="WATCH key1 key2 ... keyN (Redis >">WATCH key1 key2 ... keyN (Redis ></a></h1> 2.1.0)=
+<h1><a name="UNWATCH">UNWATCH</a></h1>
+<h1><a name="MULTI">MULTI</a></h1>
<h1><a name="COMMAND_1 ...">COMMAND_1 ...</a></h1>
<h1><a name="COMMAND_2 ...">COMMAND_2 ...</a></h1>
<h1><a name="COMMAND_N ...">COMMAND_N ...</a></h1>
-<h1><a name="EXEC or DISCARD">EXEC or DISCARD</a></h1><blockquote>MULTI, EXEC and DISCARD commands are the fundation of Redis Transactions.A Redis Transaction allows to execute a group of Redis commands in a singlestep, with two important guarantees:</blockquote>
-<ul><li> All the commands in a transaction are serialized and executed sequentially. It can never happen that a request issued by another client is served <b>in the middle</b> of the execution of a Redis transaction. This guarantees that the commands are executed as a single atomic operation.</li><li> Either all of the commands or none are processed. The EXEC command triggers the execution of all the commands in the transaction, so if a client loses the connection to the server in the context of a transaction before calling the MULTI command none of the operations are performed, instead if the EXEC command is called, all the operations are performed. An exception to this rule is when the Append Only File is enabled: every command that is part of a Redis transaction will log in the AOF as long as the operation is completed, so if the Redis server crashes or is killed by the system administrator in some hard way it is possible that only a partial number of operations are registered.</li></ul>
-<h2><a name="Usage">Usage</a></h2><blockquote>A Redis transaction is entered using the MULTI command. The command alwaysreplies with OK. At this point the user can issue multiple commands. Insteadto execute this commands Redis will "queue" them. All the commands areexecuted once EXEC is called.</blockquote>
-<blockquote>Calling DISCARD instead will flush the transaction queue and will exitthe transaction.</blockquote>
-<blockquote>The following is an example using the Ruby client:</blockquote><pre class="codeblock python" name="code">
+<h1><a name="EXEC or DISCARD">EXEC or DISCARD</a></h1>MULTI, EXEC, DISCARD and WATCH commands are the fundation of Redis Transactions.
+A Redis Transaction allows the execution of a group of Redis commands in a single
+step, with two important guarantees:<br/><br/><ul><li> All the commands in a transaction are serialized and executed sequentially. It can never happen that a request issued by another client is served <b>in the middle</b> of the execution of a Redis transaction. This guarantees that the commands are executed as a single atomic operation.</li><li> Either all of the commands or none are processed. The EXEC command triggers the execution of all the commands in the transaction, so if a client loses the connection to the server in the context of a transaction before calling the MULTI command none of the operations are performed, instead if the EXEC command is called, all the operations are performed. An exception to this rule is when the Append Only File is enabled: every command that is part of a Redis transaction will log in the AOF as long as the operation is completed, so if the Redis server crashes or is killed by the system administrator in some hard way it is possible that only a partial number of operations are registered.</li></ul>
+Since Redis 2.1.0, it's also possible to add a further guarantee to the above two, in the form of optimistic locking of a set of keys in a way very similar to a CAS (check and set) operation. This is documented later in this manual page.<h2><a name="Usage">Usage</a></h2>A Redis transaction is entered using the MULTI command. The command always
+replies with OK. At this point the user can issue multiple commands. Instead
+to execute this commands Redis will "queue" them. All the commands are
+executed once EXEC is called.<br/><br/>Calling DISCARD instead will flush the transaction queue and will exit
+the transaction.<br/><br/>The following is an example using the Ruby client:
+<pre class="codeblock python" name="code">
?> r.multi
=> "OK"
>> r.incr "foo"
>> r.exec
=> [1, 1, 2]
</pre>
-<blockquote>As it is possible to see from the session above, MULTI returns an "array" ofreplies, where every element is the reply of a single command in thetransaction, in the same order the commands were queued.</blockquote>
-<blockquote>When a Redis connection is in the context of a MULTI request, all the commandswill reply with a simple string "QUEUED" if they are correct from thepoint of view of the syntax and arity (number of arguments) of the commaand.Some command is still allowed to fail during execution time.</blockquote>
-<blockquote>This is more clear if at protocol level: in the following example one commandwill fail when executed even if the syntax is right:</blockquote><pre class="codeblock python python" name="code">
+As it is possible to see from the session above, MULTI returns an "array" of
+replies, where every element is the reply of a single command in the
+transaction, in the same order the commands were queued.<br/><br/>When a Redis connection is in the context of a MULTI request, all the commands
+will reply with a simple string "QUEUED" if they are correct from the
+point of view of the syntax and arity (number of arguments) of the commaand.
+Some command is still allowed to fail during execution time.<br/><br/>This is more clear if at protocol level: in the following example one command
+will fail when executed even if the syntax is right:
+<pre class="codeblock python python" name="code">
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
+OK
-ERR Operation against a key holding the wrong kind of value
</pre>
-<blockquote>MULTI returned a two elements bulk reply in witch one of this is a +OKcode and one is a -ERR reply. It's up to the client lib to find a sensibleway to provide the error to the user.</blockquote>
-<blockquote>IMPORTANT: even when a command will raise an error, all the other commandsin the queue will be processed. Redis will NOT stop the processing ofcommands once an error is found.</blockquote>
-<blockquote>Another example, again using the write protocol with telnet, shows howsyntax errors are reported ASAP instead:</blockquote><pre class="codeblock python python python" name="code">
+MULTI returned a two elements bulk reply in witch one of this is a +OK
+code and one is a -ERR reply. It's up to the client lib to find a sensible
+way to provide the error to the user.<br/><br/><blockquote>IMPORTANT: even when a command will raise an error, all the other commandsin the queue will be processed. Redis will NOT stop the processing ofcommands once an error is found.</blockquote>
+Another example, again using the write protocol with telnet, shows how
+syntax errors are reported ASAP instead:
+<pre class="codeblock python python python" name="code">
MULTI
+OK
INCR a b c
-ERR wrong number of arguments for 'incr' command
</pre>
-<blockquote>This time due to the syntax error the "bad" INCR command is not queuedat all.</blockquote>
-<h2><a name="The DISCARD command">The DISCARD command</a></h2><blockquote>DISCARD can be used in order to abort a transaction. No command will beexecuted, and the state of the client is again the normal one, outsideof a transaction. Example using the Ruby client:</blockquote><pre class="codeblock python python python python" name="code">
+This time due to the syntax error the "bad" INCR command is not queued
+at all.<h2><a name="The DISCARD command">The DISCARD command</a></h2>DISCARD can be used in order to abort a transaction. No command will be
+executed, and the state of the client is again the normal one, outside
+<blockquote>of a transaction. Example using the Ruby client:</blockquote><pre class="codeblock python python python python" name="code">
?> r.set("foo",1)
=> true
>> r.multi
=> "OK"
>> r.get("foo")
=> "1"
-</pre><h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>, specifically:<br/><br/><pre class="codeblock python python python python python" name="code">
-The result of a MULTI/EXEC command is a multi bulk reply where every element is the return value of every command in the atomic transaction.
+</pre><h2><a name="Check and Set (CAS) transactions using WATCH">Check and Set (CAS) transactions using WATCH</a></h2>WATCH is used in order to provide a CAS (Check and Set) behavior to
+Redis Transactions.<br/><br/>WATCHed keys are monitored in order to detect changes against this keys.
+If at least a watched key will be modified before the EXEC call, the
+whole transaction will abort, and EXEC will return a nil object
+(A Null Multi Bulk reply) to notify that the transaction failed.<br/><br/>For example imagine we have the need to atomically increment the value
+of a key by 1 (I know we have INCR, let's suppose we don't have it).<br/><br/>The first try may be the following:
+<pre class="codeblock python python python python python" name="code">
+val = GET mykey
+val = val + 1
+SET mykey $val
+</pre>
+This will work reliably only if we have a single client performing the operation in a given time.
+If multiple clients will try to increment the key about at the same time
+there will be a race condition. For instance client A and B will read the
+old value, for instance, 10. The value will be incremented to 11 by both
+the clients, and finally SET as the value of the key. So the final value
+will be "11" instead of "12".<br/><br/>Thanks to WATCH we are able to model the problem very well:
+<pre class="codeblock python python python python python python" name="code">
+WATCH mykey
+val = GET mykey
+val = val + 1
+MULTI
+SET mykey $val
+EXEC
</pre>
+Using the above code, if there are race conditions and another client
+modified the result of <i>val</i> in the time between our call to WATCH and
+our call to EXEC, the transaction will fail.<br/><br/>We'll have just to re-iterate the operation hoping this time we'll not get
+a new race. This form of locking is called <b>optimistic locking</b> and is
+a very powerful form of locking as in many problems there are multiple
+clients accessing a much bigger number of keys, so it's very unlikely that
+there are collisions: usually operations don't need to be performed
+multiple times.<h2><a name="WATCH explained">WATCH explained</a></h2>So what is WATCH really about? It is a command that will make the EXEC
+conditional: we are asking Redis to perform the transaction only if no
+other client modified any of the WATCHed keys. Otherwise the transaction is not
+entered at all. (Note that if you WATCH a volatile key and Redis expires the key after you WATCHed it, EXEC will still work. <a href="http://code.google.com/p/redis/issues/detail?id=270" target="_blank">More</a>.)<br/><br/>WATCH can be called multiple times. Simply all the WATCH calls will
+have the effects to watch for changes starting from the call, up to the
+moment EXEC is called.<br/><br/>When EXEC is called, either if it will fail or succeed, all keys are
+UNWATCHed. Also when a client connection is closed, everything gets
+UNWATCHed.<br/><br/>It is also possible to use the UNWATCH command (without arguments) in order
+to flush all the watched keys. Sometimes this is useful as we
+optimistically lock a few keys, since possibly we need to perform a transaction
+to alter those keys, but after reading the current content of the keys
+we don't want to proceed. When this happens we just call UNWATCH so that
+the connection can already be used freely for new transactions.<h2><a name="WATCH used to implement ZPOP">WATCH used to implement ZPOP</a></h2>A good example to illustrate how WATCH can be used to create new atomic
+operations otherwise not supported by Redis is to implement ZPOP, that is
+a command that pops the element with the lower score from a sorted set
+in an atomic way. This is the simplest implementation:
+<pre class="codeblock python python python python python python python" name="code">
+WATCH zset
+ele = ZRANGE zset 0 0
+MULTI
+ZREM zset ele
+EXEC
+</pre>
+If EXEC fails (returns a nil value) we just re-iterate the operation.<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>, specifically:<br/><br/><pre class="codeblock python python python python python python python python" name="code">
+The result of a MULTI/EXEC command is a multi bulk reply where every element is the return value of every command in the atomic transaction.
+</pre>If a MULTI/EXEC transaction is aborted because of WATCH detected modified keys, a <a href="ReplyTypes.html">Null Multi Bulk reply</a> is returned.
</div>
</div>
</div>
<div class="narrow">
- = Introduction =<br/><br/>Redis is a database. To be specific, Redis is a database implementing a dictionary, where every key is associated with a value. For example I can set the key "surname_1992" to the string "Smith".
-What makes Redis different from many other key-value stores, is that every single value has a type. The following types are supported:<br/><br/><ul><li> <a href="Strings.html">Strings</a></li><li> <a href="Lists.html">Lists</a></li><li> <a href="Sets.html">Sets</a></li><li> <a href="SortedSets.html">Sorted Set</a> (since version 1.1)</li></ul>
-The type of a value determines what operations (called commands) are available for the value itself.
-For example you can append elements to a list stored at the key "mylist" using the LPUSH or RPUSH command in O(1). Later you'll be able to get a range of elements with LRANGE or trim the list with LTRIM. Sets are very flexible too, it is possible to add and remove elements from Sets (unsorted collections of strings), and then ask for server-side intersection, union, difference of Sets. Each command is performed through server-side atomic operations.
-Please refer to the <a href="CommandReference.html">Command Reference</a> to see the full list of operations associated to these data types.<br/><br/>In other words, you can look at Redis as a data structures server. A Redis user is virtually provided with an interface to <a href="http://en.wikipedia.org/wiki/Abstract_data_type" target="_blank">Abstract Data Types</a>, saving her from the responsibility to implement concrete data structures and algorithms. Indeed both algorithms and data structures in Redis are properly choosed in order to obtain the best performance.<h1><a name="All data in memory, but saved on disk">All data in memory, but saved on disk</a></h1>Redis loads and mantains the whole dataset into memory, but the dataset is persistent, since at the same time it is saved on disk, so that when the server is restarted data can be loaded back in memory.<br/><br/>There are two kind of persistence supported: the first one is called snapshotting. In this mode Redis, from time to time, writes a dump on disk asynchronously. The dataset is loaded from the dump every time the server is (re)started.<br/><br/>Redis can be configured to save the dataset when a certain number of changes is reached and after a given number of seconds elapses. For example, you can configure Redis to save after 1000 changes and at most 60 seconds since the last save. You can specify any combination for these numbers.<br/><br/>Because data is written asynchronously, when a system crash occurs, the last few queries can get lost (that is acceptable in many applications but not in all). In order to make this a non issue Redis supports another, safer persistence mode, called <a href="AppendOnlyFileHowto.html">Append Only File</a>, where every command received altering the dataset (so not a read-only command, but a write command) is written on an append only file ASAP. This commands are <i>replayed</i> when the server is restarted in order to rebuild the dataset in memory.<br/><br/>Redis Append Only File supports a very handy feature: the server is able to safely rebuild the append only file in background in a non-blocking fashion when it gets too long. You can find <a href="AppendOnlyFileHowto.html">more details in the Append Only File HOWTO</a>.<h1><a name="Master-Slave replication made trivial">Master-Slave replication made trivial</a></h1>Whatever will be the persistence mode you'll use Redis supports master-slave replications if you want to stay really safe or if you need to scale to huge amounts of reads.<br/><br/><b>Redis Replication is trivial to setup</b>. So trivial that all you need to do in order to configure a Redis server to be a slave of another one, with automatic synchronization if the link will go down and so forth, is the following config line: <code name="code" class="python">slaveof 192.168.1.100 6379</code>. <a href="ReplicationHowto.html">We provide a Replication Howto</a> if you want to know more about this feature.<h1><a name="It's persistent but supports expires">It's persistent but supports expires</a></h1>Redis can be used as a <b>memcached on steroids</b> because is as fast as memcached but with a number of features more. Like memcached, Redis also supports setting timeouts to keys so that this key will be automatically removed when a given amount of time passes.<h1><a name="Beyond key-value databases">Beyond key-value databases</a></h1>All these features allow to use Redis as the sole DB for your scalable application without the need of any relational database. <a href="TwitterAlikeExample.html">We wrote a simple Twitter clone in PHP + Redis</a> to show a real world example, the link points to an article explaining the design and internals in very simple words.<h1><a name="Multiple databases support">Multiple databases support</a></h1>Redis supports multiple databases with commands to atomically move keys from one database to the other. By default DB 0 is selected for every new connection, but using the SELECT command it is possible to select a different database. The MOVE operation can move an item from one DB to another atomically. This can be used as a base for locking free algorithms together with the 'RANDOMKEY' commands.<h1><a name="Know more about Redis!">Know more about Redis!</a></h1>To really get a feeling about what Redis is and how it works please try reading <a href="IntroductionToRedisDataTypes.html">A fifteen minutes introduction to Redis data types</a>.<br/><br/>To know a bit more about how Redis works <i>internally</i> continue reading.<h1><a name="Redis Tutorial">Redis Tutorial</a></h1>(note, you can skip this section if you are only interested in "formal" doc.)<br/><br/>Later in this document you can find detailed information about Redis commands,
+ = Introduction =<br/><br/>Redis is an extremely fast and powerful key-value store database and server implemented in ANSI C. Redis offers many different ways to do one straightforward thing: store a value ("antirez") to a key ("redis"). While the format of keys must always be simple strings, the power is with the values, which support the following data types:<br/><br/><ul><li> <a href="Strings.html">Strings</a></li><li> <a href="Lists.html">Lists</a></li><li> <a href="Sets.html">Sets</a></li><li> <a href="SortedSets.html">Sorted Sets (zsets)</a></li><li> <a href="Hashes.html">Hashes</a></li></ul>
+Each value type has an associated list of commands which can operate on them, and the <a href="CommandReference.html">The Redis Command Reference</a> contains an up to date list of these commands, organized primarily by data type. The Redis source also includes a <a href="RedisCLI.html">Redis command line interface</a> which allows you to interact directly with the server, and is the means by which this introduction will provide examples. Once you walk through the <a href="QuickStart.html">Redis Quick Start Guide</a> to get your instance of Redis running, you can follow along. <br/><br/>One of the most powerful aspects of Redis is the wide range of commands which are optimized to work with specific data value types and executed as atomic server-side operations. The <a href="Lists.html">List</a> type is a great example - Redis implements O(1) operations such as <a href="RpushCommand.html">LPUSH</a> or <a href="RpushCommand.html">RPUSH</a>, which have accompanying <a href="LpopCommand.html">LPOP</a> and <a href="LpopCommand.html">RPOP</a> methods:<br/><br/><pre class="codeblock python" name="code">
+redis> lpush programming_languages C
+OK
+redis> lpush programming_languages Ruby
+OK
+redis> rpush programming_languages Python
+OK
+redis> rpop programming_languages
+Python
+redis> lpop programming_languages
+Ruby
+</pre>More complex operations are available for each data type as well. Continuing with lists, you can get a range of elements with <a href="LrangeCommand.html">LRANGE</a> (O(start+n)) or trim the list with <a href="LtrimCommand.html">LTRIM</a> (O(n)):<br/><br/><pre class="codeblock python python" name="code">
+redis> lpush cities NYC
+OK
+redis> lpush cities SF
+OK
+redis> lpush cities Tokyo
+OK
+redis> lpush cities London
+OK
+redis> lpush cities Paris
+OK
+redis> lrange cities 0 2
+1. Paris
+2. London
+3. Tokyo
+redis> ltrim cities 0 1
+OK
+redis> lpop cities
+Paris
+redis> lpop cities
+London
+redis> lpop cities
+(nil)
+</pre>You can also add and remove elements from a set, and perform intersections, unions, and differences. <br/><br/>Redis can also be looked at as a data structures server. A Redis user is virtually provided with an interface to <a href="http://en.wikipedia.org/wiki/Abstract_data_type" target="_blank">Abstract Data Types</a>, saving them from the responsibility of implementing concrete data structures and algorithms -- indeed both algorithms and data structures in Redis are properly chosen in order to obtain the best performance.<h1><a name="All data in memory, but saved on disk">All data in memory, but saved on disk</a></h1>Redis loads and mantains the whole dataset into memory, but the dataset is persistent, since at the same time it is saved on disk, so that when the server is restarted data can be loaded back in memory.<br/><br/>There are two kinds of persistence supported: the first one is called snapshotting. In this mode Redis periodically writes to disk asynchronously. The dataset is loaded from the dump every time the server is (re)started.<br/><br/>Redis can be configured to save the dataset when a certain number of changes is reached and after a given number of seconds elapses. For example, you can configure Redis to save after 1000 changes and at most 60 seconds since the last save. You can specify any combination for these numbers.<br/><br/>Because data is written asynchronously, when a system crash occurs, the last few queries can get lost (that is acceptable in many applications but not in all). In order to make this a non issue Redis supports another, safer persistence mode, called <a href="AppendOnlyFileHowto.html">Append Only File</a>, where every command received altering the dataset (so not a read-only command, but a write command) is written on an append only file ASAP. This commands are <i>replayed</i> when the server is restarted in order to rebuild the dataset in memory.<br/><br/>Redis Append Only File supports a very handy feature: the server is able to safely rebuild the append only file in background in a non-blocking fashion when it gets too long. You can find <a href="AppendOnlyFileHowto.html">more details in the Append Only File HOWTO</a>.<h1><a name="Master-Slave replication made trivial">Master-Slave replication made trivial</a></h1>Whatever will be the persistence mode you'll use Redis supports master-slave replications if you want to stay really safe or if you need to scale to huge amounts of reads.<br/><br/><b>Redis Replication is trivial to setup</b>. So trivial that all you need to do in order to configure a Redis server to be a slave of another one, with automatic synchronization if the link will go down and so forth, is the following config line: <code name="code" class="python">slaveof 192.168.1.100 6379</code>. <a href="ReplicationHowto.html">We provide a Replication Howto</a> if you want to know more about this feature.<h1><a name="It's persistent but supports expires">It's persistent but supports expires</a></h1>Redis can be used as a <b>memcached on steroids</b> because is as fast as memcached but with a number of features more. Like memcached, Redis also supports setting timeouts to keys so that this key will be automatically removed when a given amount of time passes.<h1><a name="Beyond key-value databases">Beyond key-value databases</a></h1>All these features allow to use Redis as the sole DB for your scalable application without the need of any relational database. <a href="TwitterAlikeExample.html">We wrote a simple Twitter clone in PHP + Redis</a> to show a real world example, the link points to an article explaining the design and internals in very simple words.<h1><a name="Multiple databases support">Multiple databases support</a></h1>Redis supports multiple databases with commands to atomically move keys from one database to the other. By default DB 0 is selected for every new connection, but using the SELECT command it is possible to select a different database. The MOVE operation can move an item from one DB to another atomically. This can be used as a base for locking free algorithms together with the 'RANDOMKEY' commands.<h1><a name="Know more about Redis!">Know more about Redis!</a></h1>To really get a feeling about what Redis is and how it works please try reading <a href="IntroductionToRedisDataTypes.html">A fifteen minutes introduction to Redis data types</a>.<br/><br/>To know a bit more about how Redis works <i>internally</i> continue reading.<h1><a name="Redis Tutorial">Redis Tutorial</a></h1>(note, you can skip this section if you are only interested in "formal" doc.)<br/><br/>Later in this document you can find detailed information about Redis commands,
the protocol specification, and so on. This kind of documentation is useful
but... if you are new to Redis it is also BORING! The Redis protocol is designed
so that is both pretty efficient to be parsed by computers, but simple enough
The server will start and log stuff on the standard output, if you want
it to log more edit redis.conf, set the loglevel to debug, and restart it.<br/><br/>You can specify a configuration file as unique parameter:<br/><br/><blockquote>./redis-server /etc/redis.conf</blockquote>
This is NOT required. The server will start even without a configuration file
-using a default built-in configuration.<br/><br/>Now let's try to set a key to a given value:<br/><br/><pre class="codeblock python" name="code">
+using a default built-in configuration.<br/><br/>Now let's try to set a key to a given value:<br/><br/><pre class="codeblock python python python" name="code">
$ telnet localhost 6379
Trying 127.0.0.1...
Connected to localhost.
Redis with the telnet command easily.<br/><br/>The last line of the chat between server and client is "+OK". This means
our key was added without problems. Actually SET can never fail but
the "+OK" sent lets us know that the server received everything and
-the command was actually executed.<br/><br/>Let's try to get the key content now:<br/><br/><pre class="codeblock python python" name="code">
+the command was actually executed.<br/><br/>Let's try to get the key content now:<br/><br/><pre class="codeblock python python python python" name="code">
GET foo
$3
bar
</pre>Ok that's very similar to 'set', just the other way around. We sent "get foo",
the server replied with a first line that is just the $ character follwed by
the number of bytes the value stored at key contained, followed by the actual
-bytes. Again "\r\n" are appended both to the bytes count and the actual data. In Redis slang this is called a bulk reply.<br/><br/>What about requesting a non existing key?<br/><br/><pre class="codeblock python python python" name="code">
+bytes. Again "\r\n" are appended both to the bytes count and the actual data. In Redis slang this is called a bulk reply.<br/><br/>What about requesting a non existing key?<br/><br/><pre class="codeblock python python python python python" name="code">
GET blabla
$-1
-</pre>When the key does not exist instead of the length, just the "$-1" string is sent. Since a -1 length of a bulk reply has no meaning it is used in order to specifiy a 'nil' value and distinguish it from a zero length value. Another way to check if a given key exists or not is indeed the EXISTS command:<br/><br/><pre class="codeblock python python python python" name="code">
+</pre>When the key does not exist instead of the length, just the "$-1" string is sent. Since a -1 length of a bulk reply has no meaning it is used in order to specifiy a 'nil' value and distinguish it from a zero length value. Another way to check if a given key exists or not is indeed the EXISTS command:<br/><br/><pre class="codeblock python python python python python python" name="code">
EXISTS nokey
:0
EXISTS foo
</div>
<div class="narrow">
- == Sorted Set Commands ==<br/><br/><ul><li> <a href="ZaddCommand.html">ZADD</a></li><li> <a href="ZremCommand.html">ZREM</a></li><li> <a href="ZincrbyCommand.html">ZINCRBY</a></li><li> <a href="ZrankCommand.html">ZRANK</a></li><li> <a href="ZrankCommand.html">ZREVRANK</a></li><li> <a href="ZrangeCommand.html">ZRANGE</a></li><li> <a href="ZrangeCommand.html">ZREVRANGE</a></li><li> <a href="ZrangebyscoreCommand.html">ZRANGEBYSCORE</a></li><li> <a href="ZremrangebyrankCommand.html">ZREMRANGEBYRANK</a></li><li> <a href="ZremrangebyscoreCommand.html">ZREMRANGEBYSCORE</a> </li><li> <a href="ZcardCommand.html">ZCARD</a></li><li> <a href="ZscoreCommand.html">ZSCORE</a></li><li> <a href="ZunionstoreCommand.html">ZUNION / ZINTER</a></li><li> <a href="SortCommand.html">SORT</a></li></ul>
+ == Sorted Set Commands ==<br/><br/><ul><li> <a href="ZaddCommand.html">ZADD</a></li><li> <a href="ZremCommand.html">ZREM</a></li><li> <a href="ZincrbyCommand.html">ZINCRBY</a></li><li> <a href="ZrankCommand.html">ZRANK</a></li><li> <a href="ZrankCommand.html">ZREVRANK</a></li><li> <a href="ZrangeCommand.html">ZRANGE</a></li><li> <a href="ZrangeCommand.html">ZREVRANGE</a></li><li> <a href="ZrangebyscoreCommand.html">ZRANGEBYSCORE</a></li><li> <a href="ZrangebyscoreCommand.html">ZCOUNT</a></li><li> <a href="ZremrangebyrankCommand.html">ZREMRANGEBYRANK</a></li><li> <a href="ZremrangebyscoreCommand.html">ZREMRANGEBYSCORE</a> </li><li> <a href="ZcardCommand.html">ZCARD</a></li><li> <a href="ZscoreCommand.html">ZSCORE</a></li><li> <a href="ZunionstoreCommand.html">ZUNIONSTORE</a></li><li> <a href="ZunionstoreCommand.html">ZINTERSTORE</a></li><li> <a href="SortCommand.html">SORT</a></li></ul>
</div>
</div>
<div class="narrow">
<h1><a name="Supported Languages (DRAFT)">Supported Languages (DRAFT)</a></h1>Wondering if you can use Redis from your favorite language? Well here is the definitive guide to the available client libraries.<br/><br/>This libraries are intended to expose Redis commands, but you also have the option to use some higher level libraries that provide a <b><a href="ObjectHashMappers.html">Object Hash Mappings</a></b> pretty much the same idea implemented by a classic <b>ORM</b>.<h2><a name="TODO">TODO</a></h2><ul><li> Add <a href="http://github.com/madsimian/em-redis" target="_blank">http://github.com/madsimian/em-redis</a></li><li> Add <a href="http://github.com/besquared/redis-datastructures" target="_blank">http://github.com/besquared/redis-datastructures</a></li><li> Add <a href="http://github.com/sma/redis-node-client" target="_blank">http://github.com/sma/redis-node-client</a></li></ul>
<h2><a name="Features Support Matrix">Features Support Matrix</a></h2> <br/><br/>The following matrix should give you a quick overviwe of the state of the different client libraries existing for each supported language.<br/><br/>The core command set is the one of Version 1.0, while <a href="Sharding.html">Sharding</a> and <a href="Pipelining.html">Pipelining</a> are convenient client side features not tied to any Redis server version.<h3><a name="Version 1.1">Version 1.1</a></h3>Compatible client libraries are expected to implement the command sets specified in <b>Version 1.0</b> plus:<br/><br/><ul><li> <b>String</b>: MSET, MSETNX.</li><li> <b>List</b>: RPOPLPUSH.</li><li> <b>Sorted Set (ZSET)</b>: ZADD, ZREM, ZRANGE, ZREVRANGE, ZRANGEBYSCORE, ZCARD, ZSCORE.</li></ul>
-<h3><a name="Version 1.0">Version 1.0</a></h3> <br/><br/>Compatible client libraries are expected to implement the following command sets:<br/><br/><ul><li> <b>String</b>: GET, SET, SETNX, DEL, EXISTS, INCR, DECR, MGET, INCRBY, DECRBY, GETSET, TYPE.</li><li> <b>List</b>: RPUSH, LPUSH, RPOP, LPOP, LLEN, LINDEX, LSET, LRANGE, LTRIM, LREM.</li><li> <b>Set</b>: SADD, SREM, SMOVE, SISMEMBER, SCARD, SPOP, SINTER, SINTERSTORE, SUNION, SUNIONSTORE, SDIFF, SDIFFSTORE, SMEMBERS.</li><li> <b>Keyspace</b>: KEYS, RANDOMKEY, RENAME, RENAMENX, DBSIZE, EXPIRE, TTL.</li><li> <b>Databases</b>: SELECT, MOVE, FLUSHDB, FLUSHALL.</li><li> <b>Sort</b>: SORT</li><li> <b>Connection</b>: AUTH, QUIT?. ???</li><li> <b>Persistence</b>: SAVE, BGSAVE, LASTSAVE, SHUTDOWN?. ???</li><li> <b>Server</b>: INFO, MONITOR? SLAVEOF? ???</li></ul><blockquote></blockquote><table><tr><td style="border: 1px solid #aaa; padding: 5px;"> <b>Language</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Name</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Sharding</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Pipelining</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>1.1</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>1.0</b> </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> ActionScript 3</td><td style="border: 1px solid #aaa; padding: 5px;"> as3redis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Clojure </td><td style="border: 1px solid #aaa; padding: 5px;"> redis-clojure </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Partial </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Common Lisp </td><td style="border: 1px solid #aaa; padding: 5px;"> CL-Redis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Erlang </td><td style="border: 1px solid #aaa; padding: 5px;"> erldis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Looks like </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Looks like </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Go </td><td style="border: 1px solid #aaa; padding: 5px;"> Go-Redis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Haskell </td><td style="border: 1px solid #aaa; padding: 5px;"> haskell-redis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Java </td><td style="border: 1px solid #aaa; padding: 5px;"> JDBC-Redis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Java </td><td style="border: 1px solid #aaa; padding: 5px;"> JRedis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> LUA </td><td style="border: 1px solid #aaa; padding: 5px;"> redis-lua </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Perl </td><td style="border: 1px solid #aaa; padding: 5px;"> Redis Client </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Perl </td><td style="border: 1px solid #aaa; padding: 5px;"> AnyEvent::Redis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> PHP </td><td style="border: 1px solid #aaa; padding: 5px;"> Redis PHP Bindings </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> PHP </td><td style="border: 1px solid #aaa; padding: 5px;"> phpredis (C) </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> PHP </td><td style="border: 1px solid #aaa; padding: 5px;"> Predis </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> PHP </td><td style="border: 1px solid #aaa; padding: 5px;"> Redisent </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Python </td><td style="border: 1px solid #aaa; padding: 5px;"> Python Client </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Python </td><td style="border: 1px solid #aaa; padding: 5px;"> py-redis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Partial </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Python </td><td style="border: 1px solid #aaa; padding: 5px;"> txredis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Ruby </td><td style="border: 1px solid #aaa; padding: 5px;"> redis-rb </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Scala </td><td style="border: 1px solid #aaa; padding: 5px;"> scala-redis </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> TCL </td><td style="border: 1px solid #aaa; padding: 5px;"> TCL </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr></table>
+<h3><a name="Version 1.0">Version 1.0</a></h3> <br/><br/>Compatible client libraries are expected to implement the following command sets:<br/><br/><ul><li> <b>String</b>: GET, SET, SETNX, DEL, EXISTS, INCR, DECR, MGET, INCRBY, DECRBY, GETSET, TYPE.</li><li> <b>List</b>: RPUSH, LPUSH, RPOP, LPOP, LLEN, LINDEX, LSET, LRANGE, LTRIM, LREM.</li><li> <b>Set</b>: SADD, SREM, SMOVE, SISMEMBER, SCARD, SPOP, SINTER, SINTERSTORE, SUNION, SUNIONSTORE, SDIFF, SDIFFSTORE, SMEMBERS.</li><li> <b>Keyspace</b>: KEYS, RANDOMKEY, RENAME, RENAMENX, DBSIZE, EXPIRE, TTL.</li><li> <b>Databases</b>: SELECT, MOVE, FLUSHDB, FLUSHALL.</li><li> <b>Sort</b>: SORT</li><li> <b>Connection</b>: AUTH, QUIT?. ???</li><li> <b>Persistence</b>: SAVE, BGSAVE, LASTSAVE, SHUTDOWN?. ???</li><li> <b>Server</b>: INFO, MONITOR? SLAVEOF? ???</li></ul><blockquote></blockquote><table><tr><td style="border: 1px solid #aaa; padding: 5px;"> <b>Language</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Name</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Sharding</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>Pipelining</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>1.1</b> </td><td style="border: 1px solid #aaa; padding: 5px;"> <b>1.0</b> </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> ActionScript 3</td><td style="border: 1px solid #aaa; padding: 5px;"> as3redis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Clojure </td><td style="border: 1px solid #aaa; padding: 5px;"> redis-clojure </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Partial </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Common Lisp </td><td style="border: 1px solid #aaa; padding: 5px;"> CL-Redis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Erlang </td><td style="border: 1px solid #aaa; padding: 5px;"> erldis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Looks like </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Looks like </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Go </td><td style="border: 1px solid #aaa; padding: 5px;"> Go-Redis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Haskell </td><td style="border: 1px solid #aaa; padding: 5px;"> haskell-redis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Java </td><td style="border: 1px solid #aaa; padding: 5px;"> JDBC-Redis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Java </td><td style="border: 1px solid #aaa; padding: 5px;"> JRedis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Java </td><td style="border: 1px solid #aaa; padding: 5px;"> Jedis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> LUA </td><td style="border: 1px solid #aaa; padding: 5px;"> redis-lua </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Perl </td><td style="border: 1px solid #aaa; padding: 5px;"> Redis Client </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Perl </td><td style="border: 1px solid #aaa; padding: 5px;"> AnyEvent::Redis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> PHP </td><td style="border: 1px solid #aaa; padding: 5px;"> Redis PHP Bindings </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> PHP </td><td style="border: 1px solid #aaa; padding: 5px;"> phpredis (C) </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> PHP </td><td style="border: 1px solid #aaa; padding: 5px;"> Predis </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> PHP </td><td style="border: 1px solid #aaa; padding: 5px;"> Redisent </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Python </td><td style="border: 1px solid #aaa; padding: 5px;"> Python Client </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Python </td><td style="border: 1px solid #aaa; padding: 5px;"> py-redis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Partial </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Python </td><td style="border: 1px solid #aaa; padding: 5px;"> txredis </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Ruby </td><td style="border: 1px solid #aaa; padding: 5px;"> redis-rb </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> Scala </td><td style="border: 1px solid #aaa; padding: 5px;"> scala-redis </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr><tr><td style="border: 1px solid #aaa; padding: 5px;"> TCL </td><td style="border: 1px solid #aaa; padding: 5px;"> TCL </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> No </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td><td style="border: 1px solid #aaa; padding: 5px;"> Yes </td></tr></table>
<h2><a name="Client Libraries Reference">Client Libraries Reference</a></h2><h3><a name="as3 (ActionScript 3)">as3 (ActionScript 3)</a></h3><ul><li> An ActionScript 3 (Flash) library for Redis.</li><li> Repository: <a href="http://github.com/claus/as3redis" target="_blank">http://github.com/claus/as3redis</a></li><li> Author: Claus Wahlers, <a href="http://twitter.com/cwahlers" target="_blank"> @cwahlers</a>.</li></ul>
<h3><a name="redis-clojure (Clojure)">redis-clojure (Clojure)</a></h3><ul><li> A Clojure client library for the key-value storage system Redis.</li><li> Repository: <a href="http://github.com/ragnard/redis-clojure" target="_blank">http://github.com/ragnard/redis-clojure</a></li><li> Author: Ragnar Dahlén, <a href="http://twitter.com/ragge" target="_blank">@ragge</a>.</li></ul>
<h3><a name="CL-Redis (Common Lisp)">CL-Redis (Common Lisp)</a></h3><ul><li> Common Lisp client library for Redis, an advanced key/value store.</li><li> Home Page: <a href="http://www.cliki.net/cl-redis" target="_blank">http://www.cliki.net/cl-redis</a></li><li> Author: Mahmud, <a href="http://twitter.com/BigThingist" target="_blank">@BigThingist</a>.</li></ul>
<h3><a name="Go-Redis (Go)">Go-Redis (Go)</a></h3><ul><li> Client protocol for redis key-value store.</li><li> Author: Joubin Houshyar, <a href="http://twitter.com/SunOf27" target="_blank">@SunOf27</a>.</li><li> Repository: <a href="http://github.com/alphazero/Go-Redis" target="_blank">http://github.com/alphazero/Go-Redis</a></li></ul>
<h3><a name="haskell-redis (Haskell)">haskell-redis (Haskell)</a></h3><ul><li> A Haskell binding for the Redis database.</li><li> Author: <a href="http://obvioushints.blogspot.com/" target="_blank">Alvaro Videla</a>, <a href="http://twitter.com/old_sound" target="_blank">@old_sound</a>.</li><li> Repository: <a href="http://bitbucket.org/videlalvaro/redis-haskell/wiki/Home" target="_blank"> </a></li></ul>
<h3><a name="Java">Java</a></h3><h4><a name="JDBC-Redis">JDBC-Redis</a></h4><ul><li> JDBC-Redis is Java driver using the JDBC interface for Redis Database. This project doesn't aim for a complete implementation of the JDBC specification since Redis isn't a relational database, but should provide a familiar interface to Java developers interact with Redis.</li><li> Repository: <a href="http://code.google.com/p/jdbc-redis/" target="_blank">http://code.google.com/p/jdbc-redis/</a></li></ul>
-<h4><a name="JRedis">JRedis</a></h4><ul><li> Java Client and Connectors for Redis JCA compliant. Currently offers a complete functioning Synchronous connector, Asynchronous connection and pipelining support under heavy develpment.</li><li> Author: Joubin Houshyar, <a href="http://twitter.com/SunOf27" target="_blank">@SunOf27</a>.</li><li> Home: <a href="http://code.google.com/p/jredis/" target="_blank">http://code.google.com/p/jredis/</a></li><li> Repository: <a href="http://github.com/alphazero/jredis" target="_blank">http://github.com/alphazero/jredis</a></li></ul>
+<h4><a name="JRedis">JRedis</a></h4><ul><li> Java Client and Connectors for Redis JCA compliant. Currently offers a complete functioning Synchronous connector, Asynchronous connection and pipelining support under heavy development.</li><li> Author: Joubin Houshyar, <a href="http://twitter.com/SunOf27" target="_blank">@SunOf27</a>.</li><li> Home: <a href="http://code.google.com/p/jredis/" target="_blank">http://code.google.com/p/jredis/</a></li><li> Repository: <a href="http://github.com/alphazero/jredis" target="_blank">http://github.com/alphazero/jredis</a></li></ul>
+<h4><a name="Jedis">Jedis</a></h4><ul><li> Jedis is a small and sane Redis client for Java. It aims to be easier to use by providing a more natural API. It currently supports the binary-safe protocol and pipelining. Sharding and connection pooling is on the way.</li><li> Author: Jonathan Leibiusky, <a href="http://twitter.com/xetorthio" target="_blank">@xetorthio</a>.</li><li> Repository: <a href="http://github.com/xetorthio/jedis" target="_blank">http://github.com/xetorthio/jedis</a></li></ul>
<h3><a name="redis-lua (Lua)">redis-lua (Lua)</a></h3><ul><li> A Lua client library for the redis key value storage system.</li><li> Author: <a href="http://www.clorophilla.net/blog/" target="_blank">Daniele Alessandri</a>, <a href="http://twitter.com/jol1hahn" target="_blank">@jol1hahn</a>.</li><li> Repository: <a href="http://github.com/nrk/redis-lua" target="_blank">http://github.com/nrk/redis-lua</a></li></ul>
<h3><a name="Perl">Perl</a></h3><h4><a name="Perl Client">Perl Client</a></h4><ul><li> Perl binding for Redis database.</li><li> Author: <a href="http://blog.rot13.org/" target="_blank">Dobrica Pavlinusic</a>, <a href="http://twitter.com/dpavlin" target="_blank">@dpavlin</a>.</li><li> Repository: <a href="http://svn.rot13.org/index.cgi/Redis" target="_blank">http://svn.rot13.org/index.cgi/Redis</a></li></ul>
<h4><a name="AnyEvent::Redis">AnyEvent::Redis</a></h4><ul><li> Non-blocking Redis client.</li><li> Author: <a href="http://bulknews.typepad.com/" target="_blank">Tatsuhiko Miyagawa</a>, <a href="http://twitter.com/miyagawa" target="_blank">@miyagawa</a>.</li><li> Repository: <a href="http://github.com/miyagawa/AnyEvent-Redis/" target="_blank">http://github.com/miyagawa/AnyEvent-Redis/</a></li></ul>
<i>Time complexity O(log(N)) with N being the number of elements in the sorted set</i><blockquote>If <i>member</i> already exists in the sorted set adds the <i>increment</i> to its scoreand updates the position of the element in the sorted set accordingly.If <i>member</i> does not already exist in the sorted set it is added with_increment_ as score (that is, like if the previous score was virtually zero).If <i>key</i> does not exist a new sorted set with the specified_member_ as sole member is crated. If the key exists but does not hold asorted set value an error is returned.</blockquote>
<blockquote>The score value can be the string representation of a double precision floatingpoint number. It's possible to provide a negative value to perform a decrement.</blockquote>
<blockquote>For an introduction to sorted sets check the <a href="IntroductionToRedisDataTypes.html">Introduction to Redis data types</a> page.</blockquote>
-<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically:<br/><br/><pre class="codeblock python" name="code">
-The score of the member after the increment is performed.
+<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Bulk reply</a><pre class="codeblock python" name="code">
+The new score (a double precision floating point number) represented as string.
</pre>
</div>
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
-<b>ZrangebyscoreCommand: Contents</b><br> <a href="#ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis >">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis ></a><br> <a href="#ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis >">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis ></a><br> <a href="#Exclusive intervals and infinity">Exclusive intervals and infinity</a><br> <a href="#Return value">Return value</a><br> <a href="#Examples">Examples</a>
+<b>ZrangebyscoreCommand: Contents</b><br> <a href="#ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis >">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis ></a><br> <a href="#ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis >">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis ></a><br> <a href="#ZCOUNT _key_ _min_ _max_">ZCOUNT _key_ _min_ _max_</a><br> <a href="#Exclusive intervals and infinity">Exclusive intervals and infinity</a><br> <a href="#Return value">Return value</a><br> <a href="#Examples">Examples</a>
</div>
<h1 class="wikiname">ZrangebyscoreCommand</h1>
<div class="narrow">
#sidebar <a href="SortedSetCommandsSidebar.html">SortedSetCommandsSidebar</a><h1><a name="ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis >">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` (Redis ></a></h1> 1.1) =
<h1><a name="ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis >">ZRANGEBYSCORE _key_ _min_ _max_ `[`LIMIT _offset_ _count_`]` `[`WITHSCORES`]` (Redis ></a></h1> 1.3.4) =
+<h1><a name="ZCOUNT _key_ _min_ _max_">ZCOUNT _key_ _min_ _max_</a></h1>
<i>Time complexity: O(log(N))+O(M) with N being the number of elements in the sorted set and M the number of elements returned by the command, so if M is constant (for instance you always ask for the first ten elements with LIMIT) you can consider it O(log(N))</i><blockquote>Return the all the elements in the sorted set at key with a score between_min_ and <i>max</i> (including elements with score equal to min or max).</blockquote>
<blockquote>The elements having the same score are returned sorted lexicographically asASCII strings (this follows from a property of Redis sorted sets and does notinvolve further computation).</blockquote>
-<blockquote>Using the optional LIMIT it's possible to get only a range of the matchingelements in an SQL-alike way. Note that if <i>offset</i> is large the commandsneeds to traverse the list for <i>offset</i> elements and this adds up to theO(M) figure.</blockquote><h2><a name="Exclusive intervals and infinity">Exclusive intervals and infinity</a></h2>
+<blockquote>Using the optional LIMIT it's possible to get only a range of the matchingelements in an SQL-alike way. Note that if <i>offset</i> is large the commandsneeds to traverse the list for <i>offset</i> elements and this adds up to theO(M) figure.</blockquote>
+<blockquote>The <b>ZCOUNT</b> command is similar to <b>ZRANGEBYSCORE</b> but instead of returningthe actual elements in the specified interval, it just returns the numberof matching elements.</blockquote><h2><a name="Exclusive intervals and infinity">Exclusive intervals and infinity</a></h2>
<i>min</i> and <i>max</i> can be -inf and +inf, so that you are not required to know what's the greatest or smallest element in order to take, for instance, elements "up to a given value".<br/><br/>Also while the interval is for default closed (inclusive) it's possible to specify open intervals prefixing the score with a "(" character, so for instance:
<pre class="codeblock python" name="code">
ZRANGEBYSCORE zset (1.3 5
ZRANGEBYSCORE zset (5 (10
</pre>
Will return all the values with score <b>> 5 and < 10</b> (5 and 10 excluded).
-<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Multi bulk reply</a>, specifically a list of elements in the specified score range.
+<h2><a name="Return value">Return value</a></h2>ZRANGEBYSCORE returns a <a href="ReplyTypes.html">Multi bulk reply</a> specifically a list of elements in the specified score range.<br/><br/>ZCOUNT returns a <a href="ReplyTypes.html">Integer reply</a> specifically the number of elements matching the specified score range.
<h2><a name="Examples">Examples</a></h2>
<pre class="codeblock python python python" name="code">
redis> zadd zset 1 foo
2. "bar"
3. "biz"
4. "foz"
+redis> zcount zset 1 2
+(integer) 2
redis> zrangebyscore zset 1 2
1. "foo"
2. "bar"
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
-<b>ZunionCommand: Contents</b><br> <a href="#ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#Return value">Return value</a>
+<b>ZunionCommand: Contents</b><br> <a href="#ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#Return value">Return value</a>
</div>
<h1 class="wikiname">ZunionCommand</h1>
<div class="narrow">
-<h1><a name="ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.5) =<br/><br/><i>Time complexity: O(N) + O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set</i><blockquote>Creates a union or intersection of <i>N</i> sorted sets given by keys <i>k1</i> through <i>kN</i>, and stores it at <i>dstkey</i>. It is mandatory to provide the number of input keys <i>N</i>, before passing the input keys and the other (optional) arguments.</blockquote>
-<blockquote>As the terms imply, the ZINTER command requires an element to be present in each of the given inputs to be inserted in the result. The ZUNION command inserts all elements across all inputs.</blockquote>
+<h1><a name="ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.12) =
+<h1><a name="ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.12) =<br/><br/><i>Time complexity: O(N) + O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set</i><blockquote>Creates a union or intersection of <i>N</i> sorted sets given by keys <i>k1</i> through <i>kN</i>, and stores it at <i>dstkey</i>. It is mandatory to provide the number of input keys <i>N</i>, before passing the input keys and the other (optional) arguments.</blockquote>
+<blockquote>As the terms imply, the ZINTERSTORE command requires an element to be present in each of the given inputs to be inserted in the result. The ZUNIONSTORE command inserts all elements across all inputs.</blockquote>
<blockquote>Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means that the score of each element in the sorted set is first multiplied by this weight before being passed to the aggregation. When this option is not given, all weights default to 1.</blockquote>
<blockquote>With the AGGREGATE option, it's possible to specify how the results of the union or intersection are aggregated. This option defaults to SUM, where the score of an element is summed across the inputs where it exists. When this option is set to be either MIN or MAX, the resulting set will contain the minimum or maximum score of an element across the inputs where it exists.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically the number of elements in the sorted set at <i>dstkey</i>.
<div id="pagecontent">
<div class="index">
<!-- This is a (PRE) block. Make sure it's left aligned or your toc title will be off. -->
-<b>ZunionstoreCommand: Contents</b><br> <a href="#ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#Return value">Return value</a>
+<b>ZunionstoreCommand: Contents</b><br> <a href="#ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a><br> <a href="#Return value">Return value</a>
</div>
<h1 class="wikiname">ZunionstoreCommand</h1>
<div class="narrow">
-<h1><a name="ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNION / ZINTER _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.5) =<br/><br/><i>Time complexity: O(N) + O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set</i><blockquote>Creates a union or intersection of <i>N</i> sorted sets given by keys <i>k1</i> through <i>kN</i>, and stores it at <i>dstkey</i>. It is mandatory to provide the number of input keys <i>N</i>, before passing the input keys and the other (optional) arguments.</blockquote>
-<blockquote>As the terms imply, the ZINTER command requires an element to be present in each of the given inputs to be inserted in the result. The ZUNION command inserts all elements across all inputs.</blockquote>
+<h1><a name="ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZUNIONSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.12) =
+<h1><a name="ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis >">ZINTERSTORE _dstkey_ _N_ _k1_ ... _kN_ `[`WEIGHTS _w1_ ... _wN_`]` `[`AGGREGATE SUM|MIN|MAX`]` (Redis ></a></h1> 1.3.12) =
+<i>Time complexity: O(N) + O(M log(M)) with N being the sum of the sizes of the input sorted sets, and M being the number of elements in the resulting sorted set</i><blockquote>Creates a union or intersection of <i>N</i> sorted sets given by keys <i>k1</i> through <i>kN</i>, and stores it at <i>dstkey</i>. It is mandatory to provide the number of input keys <i>N</i>, before passing the input keys and the other (optional) arguments.</blockquote>
+<blockquote>As the terms imply, the ZINTERSTORE command requires an element to be present in each of the given inputs to be inserted in the result. The ZUNIONSTORE command inserts all elements across all inputs.</blockquote>
<blockquote>Using the WEIGHTS option, it is possible to add weight to each input sorted set. This means that the score of each element in the sorted set is first multiplied by this weight before being passed to the aggregation. When this option is not given, all weights default to 1.</blockquote>
<blockquote>With the AGGREGATE option, it's possible to specify how the results of the union or intersection are aggregated. This option defaults to SUM, where the score of an element is summed across the inputs where it exists. When this option is set to be either MIN or MAX, the resulting set will contain the minimum or maximum score of an element across the inputs where it exists.</blockquote>
<h2><a name="Return value">Return value</a></h2><a href="ReplyTypes.html">Integer reply</a>, specifically the number of elements in the sorted set at <i>dstkey</i>.
</div>
<div class="narrow">
- = Redis Documentation =<br/><br/><a href="http://pyha.ru/wiki/index.php?title=Redis:index" target="_blank">Russian Translation</a>Hello! The followings are pointers to different parts of the Redis Documentation.<br/><br/><ul><li> New! You can now <a href="http://try.redis-db.com" target="_blank">Try Redis directly in your browser!</a>.</li><li> <a href="README.html">The README</a> is the best starting point to know more about the project.</li><li> <a href="QuickStart.html">This short Quick Start</a> provides a five minutes step-by-step istructions on how to download, compile, run and test the basic workings of a Redis server.</li><li> <a href="CommandReference.html">The command reference</a> is a description of all the Redis commands with links to command specific pages. You can also download the <a href="http://go2.wordpress.com/?id=725X1342&site=masonoise.wordpress.com&url=http%3A%2F%2Fmasonoise.files.wordpress.com%2F2010%2F03%2Fredis-cheatsheet-v1.pdf" target="_blank">Redis Commands Cheat-Sheet</a> provided by Mason Jones (btw some command may be missing, the primary source is the wiki).</li><li> <a href="TwitterAlikeExample.html">This is a tuturial about creating a Twitter clone using *only* Redis as database, no relational DB at all is used</a>, it is a good start to understand the key-value database paradigm.</li><li> <a href="IntroductionToRedisDataTypes.html">A Fifteen Minutes Introduction to the Redis Data Types</a> explains how Redis data types work and the basic patterns of working with Redis.</li><li> <a href="http://simonwillison.net/static/2010/redis-tutorial/" target="_blank">the Simon Willison Redis Tutorial</a> is a <b>must read</b>, very good documentation where you will find a lot of real world ideas and use cases.</li><li> <a href="Features.html">The features page</a> (currently in draft) is a good start to understand the strength and limitations of Redis.</li><li> <a href="Benchmarks.html">The benchmark page</a> is about the speed performances of Redis.</li><li> <a href="FAQ.html">Our FAQ</a> contains of course some answers to common questions about Redis.</li><li> <a href="http://www.rediscookbook.org/" target="_blank">The Redis Cookbook</a> is a collaborative effort to provide some good recipe ;)</li></ul>
-<h1><a name="HOWTOs about selected features">HOWTOs about selected features</a></h1><ul><li> <a href="ReplicationHowto.html">The Redis Replication HOWTO</a> is what you need to read in order to understand how Redis master <code name="code" class="python"><-></code> slave replication works.</li><li> <a href="AppendOnlyFileHowto.html">The Append Only File HOWTO</a> explains how the alternative Redis durability mode works. AOF is an alternative to snapshotting on disk from time to time (the default).</li><li> <a href="VirtualMemoryUserGuide.html">Virutal Memory User Guide</a>. A simple to understand guide about using and configuring the Redis Virtual Memory.</li></ul>
+ = Redis Documentation =<br/><br/><a href="http://pyha.ru/wiki/index.php?title=Redis:index" target="_blank">Russian Translation</a>Hello! The followings are pointers to different parts of the Redis Documentation.<br/><br/><ul><li> New! You can now <a href="http://try.redis-db.com" target="_blank">Try Redis directly in your browser!</a>.</li><li> <a href="README.html">The README</a> is the best starting point to know more about the project.</li><li> <a href="QuickStart.html">This short Quick Start</a> provides a five minutes step-by-step istructions on how to download, compile, run and test the basic workings of a Redis server.</li><li> <a href="CommandReference.html">The command reference</a> is a description of all the Redis commands with links to command specific pages. You can also download the <a href="http://go2.wordpress.com/?id=725X1342&site=masonoise.wordpress.com&url=http%3A%2F%2Fmasonoise.files.wordpress.com%2F2010%2F03%2Fredis-cheatsheet-v1.pdf" target="_blank">Redis Commands Cheat-Sheet</a> provided by Mason Jones (btw some command may be missing, the primary source is the wiki).</li><li> <a href="TwitterAlikeExample.html">This is a tutorial about creating a Twitter clone using *only* Redis as database, no relational DB at all is used</a>, it is a good start to understand the key-value database paradigm.</li><li> <a href="IntroductionToRedisDataTypes.html">A Fifteen Minutes Introduction to the Redis Data Types</a> explains how Redis data types work and the basic patterns of working with Redis.</li><li> <a href="http://simonwillison.net/static/2010/redis-tutorial/" target="_blank">the Simon Willison Redis Tutorial</a> is a <b>must read</b>, very good documentation where you will find a lot of real world ideas and use cases.</li><li> <a href="Features.html">The features page</a> (currently in draft) is a good start to understand the strength and limitations of Redis.</li><li> <a href="Benchmarks.html">The benchmark page</a> is about the speed performances of Redis.</li><li> <a href="FAQ.html">Our FAQ</a> contains of course some answers to common questions about Redis.</li><li> <a href="http://www.rediscookbook.org/" target="_blank">The Redis Cookbook</a> is a collaborative effort to provide some good recipe ;)</li></ul>
+<h1><a name="HOWTOs about selected features">HOWTOs about selected features</a></h1><ul><li> <a href="ReplicationHowto.html">The Redis Replication HOWTO</a> is what you need to read in order to understand how Redis master <code name="code" class="python"><-></code> slave replication works.</li><li> <a href="AppendOnlyFileHowto.html">The Append Only File HOWTO</a> explains how the alternative Redis durability mode works. AOF is an alternative to snapshotting on disk from time to time (the default).</li><li> <a href="VirtualMemoryUserGuide.html">Virtual Memory User Guide</a>. A simple to understand guide about using and configuring the Redis Virtual Memory.</li></ul>
<h1><a name="Hacking">Hacking</a></h1>
<ul><li> <a href="ProtocolSpecification.html">The Protocol Specification</a> is all you need in order to implement a Redis client library for a missing language. PHP, Python, Ruby and Erlang are already supported.</li></ul>
<ul><li> Look at <a href="RedisInternals.html">Redis Internals</a> if you are interested in the implementation details of the Redis server.</li></ul>
-<h1><a name="Videos">Videos</a></h1><ul><li> <a href="http://mwrc2009.confreaks.com/13-mar-2009-19-24-redis-key-value-nirvana-ezra-zygmuntowicz.html" target="_blank">watch the Ezra Zygmuntowicz talk about Redis</a> to know the most important Redis ideas in few minutes.</li></ul>
+<h1><a name="Videos">Videos</a></h1><ul><li> <a href="http://mwrc2009.confreaks.com/13-mar-2009-19-24-redis-key-value-nirvana-ezra-zygmuntowicz.html" target="_blank">watch the Ezra Zygmuntowicz talk about Redis</a> to know the most important Redis ideas in few minutes.</li><li> <a href="http://www.ustream.tv/recorded/7855635" target="_blank">Salvatore Sanfilippo and Pieter Noordhuis at the SF Redis Meetup</a></li></ul>
</div>
</div>
CCOPT= $(CFLAGS) $(CCLINK) $(ARCH) $(PROF)
DEBUG?= -g -rdynamic -ggdb
-INSTALL_TOP= /usr/local
-INSTALL_BIN= $(INSTALL_TOP)/bin
+PREFIX= /usr/local
+INSTALL_BIN= $(PREFIX)/bin
INSTALL= cp -p
-OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o vm.o pubsub.o multi.o debug.o sort.o
+OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o vm.o pubsub.o multi.o debug.o sort.o intset.o
BENCHOBJ = ae.o anet.o redis-benchmark.o sds.o adlist.o zmalloc.o
CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o linenoise.o
CHECKDUMPOBJ = redis-check-dump.o lzf_c.o lzf_d.o
all: redis-server redis-benchmark redis-cli redis-check-dump redis-check-aof
+
# Deps (use make dep to generate this)
adlist.o: adlist.c adlist.h zmalloc.h
ae.o: ae.c ae.h zmalloc.h config.h ae_kqueue.c
ae_kqueue.o: ae_kqueue.c
ae_select.o: ae_select.c
anet.o: anet.c fmacros.h anet.h
+aof.o: aof.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+config.o: config.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+db.o: db.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+debug.o: debug.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h sha1.h
dict.o: dict.c fmacros.h dict.h zmalloc.h
+intset.o: intset.c intset.h zmalloc.h
linenoise.o: linenoise.c fmacros.h
lzf_c.o: lzf_c.c lzfP.h
lzf_d.o: lzf_d.c lzfP.h
+multi.o: multi.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+networking.o: networking.c redis.h fmacros.h config.h ae.h sds.h dict.h \
+ adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+object.o: object.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
pqsort.o: pqsort.c
+pubsub.o: pubsub.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+rdb.o: rdb.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h lzf.h
redis-benchmark.o: redis-benchmark.c fmacros.h ae.h anet.h sds.h adlist.h \
zmalloc.h
redis-check-aof.o: redis-check-aof.c fmacros.h config.h
redis-check-dump.o: redis-check-dump.c lzf.h
-redis-cli.o: redis-cli.c fmacros.h anet.h sds.h adlist.h zmalloc.h \
- linenoise.h
-redis.o: redis.c fmacros.h config.h redis.h ae.h sds.h anet.h dict.h \
- adlist.h zmalloc.h lzf.h pqsort.h zipmap.h ziplist.h sha1.h
+redis-cli.o: redis-cli.c fmacros.h version.h anet.h sds.h adlist.h \
+ zmalloc.h linenoise.h
+redis.o: redis.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
release.o: release.c release.h
+replication.o: replication.c redis.h fmacros.h config.h ae.h sds.h dict.h \
+ adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
sds.o: sds.c sds.h zmalloc.h
sha1.o: sha1.c sha1.h
+sort.o: sort.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h pqsort.h
+t_hash.o: t_hash.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+t_list.o: t_list.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+t_set.o: t_set.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+t_string.o: t_string.c redis.h fmacros.h config.h ae.h sds.h dict.h \
+ adlist.h zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+t_zset.o: t_zset.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+util.o: util.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
+vm.o: vm.c redis.h fmacros.h config.h ae.h sds.h dict.h adlist.h \
+ zmalloc.h anet.h zipmap.h ziplist.h intset.h version.h
ziplist.o: ziplist.c zmalloc.h ziplist.h
zipmap.o: zipmap.c zmalloc.h
zmalloc.o: zmalloc.c config.h
make PROF="-pg" ARCH="-arch i386"
install: all
+ mkdir -p $(INSTALL_BIN)
$(INSTALL) $(PRGNAME) $(INSTALL_BIN)
$(INSTALL) $(BENCHPRGNAME) $(INSTALL_BIN)
$(INSTALL) $(CLIPRGNAME) $(INSTALL_BIN)
c->querybuf = sdsempty();
c->argc = 0;
c->argv = NULL;
+ c->bufpos = 0;
c->flags = 0;
/* We set the fake client as a slave waiting for the synchronization
* so that Redis will not try to send replies to this client. */
fakeClient->argc = argc;
fakeClient->argv = argv;
cmd->proc(fakeClient);
- /* Discard the reply objects list from the fake client */
- while(listLength(fakeClient->reply))
- listDelNode(fakeClient->reply,listFirst(fakeClient->reply));
+
+ /* The fake client should not have a reply */
+ redisAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0);
+
/* Clean up, ready for the next command */
for (j = 0; j < argc; j++) decrRefCount(argv[j]);
zfree(argv);
+
/* Handle swapping while loading big datasets when VM is on */
force_swapout = 0;
if ((zmalloc_used_memory() - server.vm_max_memory) > 1024*1024*32)
}
exit(1);
fmterr:
- redisLog(REDIS_WARNING,"Bad file format reading the append only file");
+ redisLog(REDIS_WARNING,"Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>");
exit(1);
}
redisPanic("Unknown list encoding");
}
} else if (o->type == REDIS_SET) {
- /* Emit the SADDs needed to rebuild the set */
- dict *set = o->ptr;
- dictIterator *di = dictGetIterator(set);
- dictEntry *de;
+ char cmd[]="*3\r\n$4\r\nSADD\r\n";
- while((de = dictNext(di)) != NULL) {
- char cmd[]="*3\r\n$4\r\nSADD\r\n";
- robj *eleobj = dictGetEntryKey(de);
-
- if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
- if (fwriteBulkObject(fp,&key) == 0) goto werr;
- if (fwriteBulkObject(fp,eleobj) == 0) goto werr;
+ /* Emit the SADDs needed to rebuild the set */
+ if (o->encoding == REDIS_ENCODING_INTSET) {
+ int ii = 0;
+ int64_t llval;
+ while(intsetGet(o->ptr,ii++,&llval)) {
+ if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
+ if (fwriteBulkObject(fp,&key) == 0) goto werr;
+ if (fwriteBulkLongLong(fp,llval) == 0) goto werr;
+ }
+ } else if (o->encoding == REDIS_ENCODING_HT) {
+ dictIterator *di = dictGetIterator(o->ptr);
+ dictEntry *de;
+ while((de = dictNext(di)) != NULL) {
+ robj *eleobj = dictGetEntryKey(de);
+ if (fwrite(cmd,sizeof(cmd)-1,1,fp) == 0) goto werr;
+ if (fwriteBulkObject(fp,&key) == 0) goto werr;
+ if (fwriteBulkObject(fp,eleobj) == 0) goto werr;
+ }
+ dictReleaseIterator(di);
+ } else {
+ redisPanic("Unknown set encoding");
}
- dictReleaseIterator(di);
} else if (o->type == REDIS_ZSET) {
/* Emit the ZADDs needed to rebuild the sorted set */
zset *zs = o->ptr;
void bgrewriteaofCommand(redisClient *c) {
if (server.bgrewritechildpid != -1) {
- addReplySds(c,sdsnew("-ERR background append only file rewriting already in progress\r\n"));
+ addReplyError(c,"Background append only file rewriting already in progress");
return;
}
if (rewriteAppendOnlyFileBackground() == REDIS_OK) {
- char *status = "+Background append only file rewriting started\r\n";
- addReplySds(c,sdsnew(status));
+ addReplyStatus(c,"Background append only file rewriting started");
} else {
addReply(c,shared.err);
}
server.list_max_ziplist_entries = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2){
server.list_max_ziplist_value = memtoll(argv[1], NULL);
+ } else if (!strcasecmp(argv[0],"set-max-intset-entries") && argc == 2){
+ server.set_max_intset_entries = memtoll(argv[1], NULL);
} else {
err = "Bad directive or wrong number of arguments"; goto loaderr;
}
stopAppendOnly();
} else {
if (startAppendOnly() == REDIS_ERR) {
- addReplySds(c,sdscatprintf(sdsempty(),
- "-ERR Unable to turn on AOF. Check server logs.\r\n"));
+ addReplyError(c,
+ "Unable to turn on AOF. Check server logs.");
decrRefCount(o);
return;
}
}
sdsfreesplitres(v,vlen);
} else {
- addReplySds(c,sdscatprintf(sdsempty(),
- "-ERR not supported CONFIG parameter %s\r\n",
- (char*)c->argv[2]->ptr));
+ addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
+ (char*)c->argv[2]->ptr);
decrRefCount(o);
return;
}
return;
badfmt: /* Bad format errors */
- addReplySds(c,sdscatprintf(sdsempty(),
- "-ERR invalid argument '%s' for CONFIG SET '%s'\r\n",
+ addReplyErrorFormat(c,"Invalid argument '%s' for CONFIG SET '%s'",
(char*)o->ptr,
- (char*)c->argv[2]->ptr));
+ (char*)c->argv[2]->ptr);
decrRefCount(o);
}
void configGetCommand(redisClient *c) {
robj *o = getDecodedObject(c->argv[2]);
- robj *lenobj = createObject(REDIS_STRING,NULL);
+ void *replylen = addDeferredMultiBulkLength(c);
char *pattern = o->ptr;
int matches = 0;
- addReply(c,lenobj);
- decrRefCount(lenobj);
-
if (stringmatch(pattern,"dbfilename",0)) {
addReplyBulkCString(c,"dbfilename");
addReplyBulkCString(c,server.dbfilename);
matches++;
}
decrRefCount(o);
- lenobj->ptr = sdscatprintf(sdsempty(),"*%d\r\n",matches*2);
+ setDeferredMultiBulkLength(c,replylen,matches*2);
}
void configCommand(redisClient *c) {
server.stat_starttime = time(NULL);
addReply(c,shared.ok);
} else {
- addReplySds(c,sdscatprintf(sdsempty(),
- "-ERR CONFIG subcommand must be one of GET, SET, RESETSTAT\r\n"));
+ addReplyError(c,
+ "CONFIG subcommand must be one of GET, SET, RESETSTAT");
}
return;
badarity:
- addReplySds(c,sdscatprintf(sdsempty(),
- "-ERR Wrong number of arguments for CONFIG %s\r\n",
- (char*) c->argv[1]->ptr));
+ addReplyErrorFormat(c,"Wrong number of arguments for CONFIG %s",
+ (char*) c->argv[1]->ptr);
}
#define redis_stat stat
#endif
+/* test for proc filesystem */
+#ifdef __linux__
+#define HAVE_PROCFS 1
+#endif
+
+/* test for task_info() */
+#if defined(__APPLE__)
+#define HAVE_TASKINFO 1
+#endif
+
/* test for backtrace() */
#if defined(__APPLE__) || defined(__linux__)
#define HAVE_BACKTRACE 1
/* Delete a key, value, and associated expiration entry if any, from the DB */
int dbDelete(redisDb *db, robj *key) {
+ /* If VM is enabled make sure to awake waiting clients for this key:
+ * deleting the key will kill the I/O thread bringing the key from swap
+ * to memory, so the client will never be notified and unblocked if we
+ * don't do it now. */
+ if (server.vm_enabled) handleClientsBlockedOnSwappedKey(db,key);
/* Deleting an entry from the expires dict will not free the sds of
* the key, because it is shared with the main dictionary. */
if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
int id = atoi(c->argv[1]->ptr);
if (selectDb(c,id) == REDIS_ERR) {
- addReplySds(c,sdsnew("-ERR invalid DB index\r\n"));
+ addReplyError(c,"invalid DB index");
} else {
addReply(c,shared.ok);
}
dictIterator *di;
dictEntry *de;
sds pattern = c->argv[1]->ptr;
- int plen = sdslen(pattern);
+ int plen = sdslen(pattern), allkeys;
unsigned long numkeys = 0;
- robj *lenobj = createObject(REDIS_STRING,NULL);
+ void *replylen = addDeferredMultiBulkLength(c);
di = dictGetIterator(c->db->dict);
- addReply(c,lenobj);
- decrRefCount(lenobj);
+ allkeys = (pattern[0] == '*' && pattern[1] == '\0');
while((de = dictNext(di)) != NULL) {
sds key = dictGetEntryKey(de);
robj *keyobj;
- if ((pattern[0] == '*' && pattern[1] == '\0') ||
- stringmatchlen(pattern,plen,key,sdslen(key),0)) {
+ if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) {
keyobj = createStringObject(key,sdslen(key));
if (expireIfNeeded(c->db,keyobj) == 0) {
addReplyBulk(c,keyobj);
}
}
dictReleaseIterator(di);
- lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",numkeys);
+ setDeferredMultiBulkLength(c,replylen,numkeys);
}
void dbsizeCommand(redisClient *c) {
- addReplySds(c,
- sdscatprintf(sdsempty(),":%lu\r\n",dictSize(c->db->dict)));
+ addReplyLongLong(c,dictSize(c->db->dict));
}
void lastsaveCommand(redisClient *c) {
- addReplySds(c,
- sdscatprintf(sdsempty(),":%lu\r\n",server.lastsave));
+ addReplyLongLong(c,server.lastsave);
}
void typeCommand(redisClient *c) {
o = lookupKeyRead(c->db,c->argv[1]);
if (o == NULL) {
- type = "+none";
+ type = "none";
} else {
switch(o->type) {
- case REDIS_STRING: type = "+string"; break;
- case REDIS_LIST: type = "+list"; break;
- case REDIS_SET: type = "+set"; break;
- case REDIS_ZSET: type = "+zset"; break;
- case REDIS_HASH: type = "+hash"; break;
- default: type = "+unknown"; break;
+ case REDIS_STRING: type = "string"; break;
+ case REDIS_LIST: type = "list"; break;
+ case REDIS_SET: type = "set"; break;
+ case REDIS_ZSET: type = "zset"; break;
+ case REDIS_HASH: type = "hash"; break;
+ default: type = "unknown"; break;
}
}
- addReplySds(c,sdsnew(type));
- addReply(c,shared.crlf);
+ addReplyStatus(c,type);
}
void saveCommand(redisClient *c) {
if (server.bgsavechildpid != -1) {
- addReplySds(c,sdsnew("-ERR background save in progress\r\n"));
+ addReplyError(c,"Background save already in progress");
return;
}
if (rdbSave(server.dbfilename) == REDIS_OK) {
void bgsaveCommand(redisClient *c) {
if (server.bgsavechildpid != -1) {
- addReplySds(c,sdsnew("-ERR background save already in progress\r\n"));
+ addReplyError(c,"Background save already in progress");
return;
}
if (rdbSaveBackground(server.dbfilename) == REDIS_OK) {
- char *status = "+Background saving started\r\n";
- addReplySds(c,sdsnew(status));
+ addReplyStatus(c,"Background saving started");
} else {
addReply(c,shared.err);
}
void shutdownCommand(redisClient *c) {
if (prepareForShutdown() == REDIS_OK)
exit(0);
- addReplySds(c, sdsnew("-ERR Errors trying to SHUTDOWN. Check logs.\r\n"));
+ addReplyError(c,"Errors trying to SHUTDOWN. Check logs.");
}
void renameGenericCommand(redisClient *c, int nx) {
}
void ttlCommand(redisClient *c) {
- time_t expire;
- int ttl = -1;
+ time_t expire, ttl = -1;
expire = getExpire(c->db,c->argv[1]);
if (expire != -1) {
- ttl = (int) (expire-time(NULL));
+ ttl = (expire-time(NULL));
if (ttl < 0) ttl = -1;
}
- addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",ttl));
+ addReplyLongLong(c,(long long)ttl);
}
void persistCommand(redisClient *c) {
}
listTypeReleaseIterator(li);
} else if (o->type == REDIS_SET) {
- dict *set = o->ptr;
- dictIterator *di = dictGetIterator(set);
- dictEntry *de;
-
- while((de = dictNext(di)) != NULL) {
- robj *eleobj = dictGetEntryKey(de);
-
- xorObjectDigest(digest,eleobj);
+ setTypeIterator *si = setTypeInitIterator(o);
+ robj *ele;
+ while((ele = setTypeNext(si)) != NULL) {
+ xorObjectDigest(digest,ele);
+ decrRefCount(ele);
}
- dictReleaseIterator(di);
+ setTypeReleaseIterator(si);
} else if (o->type == REDIS_ZSET) {
zset *zs = o->ptr;
dictIterator *di = dictGetIterator(zs->dict);
char *strenc;
strenc = strEncoding(val->encoding);
- addReplySds(c,sdscatprintf(sdsempty(),
- "+Value at:%p refcount:%d "
- "encoding:%s serializedlength:%lld\r\n",
+ addReplyStatusFormat(c,
+ "Value at:%p refcount:%d "
+ "encoding:%s serializedlength:%lld",
(void*)val, val->refcount,
- strenc, (long long) rdbSavedObjectLen(val,NULL)));
+ strenc, (long long) rdbSavedObjectLen(val,NULL));
} else {
vmpointer *vp = (vmpointer*) val;
- addReplySds(c,sdscatprintf(sdsempty(),
- "+Value swapped at: page %llu "
- "using %llu pages\r\n",
+ addReplyStatusFormat(c,
+ "Value swapped at: page %llu "
+ "using %llu pages",
(unsigned long long) vp->page,
- (unsigned long long) vp->usedpages));
+ (unsigned long long) vp->usedpages);
}
} else if (!strcasecmp(c->argv[1]->ptr,"swapin") && c->argc == 3) {
lookupKeyRead(c->db,c->argv[2]);
vmpointer *vp;
if (!server.vm_enabled) {
- addReplySds(c,sdsnew("-ERR Virtual Memory is disabled\r\n"));
+ addReplyError(c,"Virtual Memory is disabled");
return;
}
if (!de) {
val = dictGetEntryVal(de);
/* Swap it */
if (val->storage != REDIS_VM_MEMORY) {
- addReplySds(c,sdsnew("-ERR This key is not in memory\r\n"));
+ addReplyError(c,"This key is not in memory");
} else if (val->refcount != 1) {
- addReplySds(c,sdsnew("-ERR Object is shared\r\n"));
+ addReplyError(c,"Object is shared");
} else if ((vp = vmSwapObjectBlocking(val)) != NULL) {
dictGetEntryVal(de) = vp;
addReply(c,shared.ok);
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"digest") && c->argc == 2) {
unsigned char digest[20];
- sds d = sdsnew("+");
+ sds d = sdsempty();
int j;
computeDatasetDigest(digest);
for (j = 0; j < 20; j++)
d = sdscatprintf(d, "%02x",digest[j]);
-
- d = sdscatlen(d,"\r\n",2);
- addReplySds(c,d);
+ addReplyStatus(c,d);
+ sdsfree(d);
} else {
- addReplySds(c,sdsnew(
- "-ERR Syntax error, try DEBUG [SEGFAULT|OBJECT <key>|SWAPIN <key>|SWAPOUT <key>|RELOAD]\r\n"));
+ addReplyError(c,
+ "Syntax error, try DEBUG [SEGFAULT|OBJECT <key>|SWAPIN <key>|SWAPOUT <key>|RELOAD]");
}
}
/* Using dictEnableResize() / dictDisableResize() we make possible to
* enable/disable resizing of the hash table as needed. This is very important
* for Redis, as we use copy-on-write and don't want to move too much memory
- * around when there is a child performing saving operations. */
+ * around when there is a child performing saving operations.
+ *
+ * Note that even when dict_can_resize is set to 0, not all resizes are
+ * prevented: an hash table is still allowed to grow if the ratio between
+ * the number of elements and the buckets > dict_force_resize_ratio. */
static int dict_can_resize = 1;
+static unsigned int dict_force_resize_ratio = 5;
/* -------------------------- private prototypes ---------------------------- */
}
/* Resize the table to the minimal size that contains all the elements,
- * but with the invariant of a USER/BUCKETS ration near to <= 1 */
+ * but with the invariant of a USER/BUCKETS ratio near to <= 1 */
int dictResize(dict *d)
{
int minimal;
/* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *d)
{
- /* If the hash table is empty expand it to the intial size,
- * if the table is "full" dobule its size. */
+ /* Incremental rehashing already in progress. Return. */
if (dictIsRehashing(d)) return DICT_OK;
- if (d->ht[0].size == 0)
- return dictExpand(d, DICT_HT_INITIAL_SIZE);
- if (d->ht[0].used >= d->ht[0].size && dict_can_resize)
+
+ /* If the hash table is empty expand it to the intial size. */
+ if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);
+
+ /* If we reached the 1:1 ratio, and we are allowed to resize the hash
+ * table (global setting) or we should avoid it but the ratio between
+ * elements/buckets is over the "safe" threshold, we resize doubling
+ * the number of buckets. */
+ if (d->ht[0].used >= d->ht[0].size &&
+ (dict_can_resize ||
+ d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
+ {
return dictExpand(d, ((d->ht[0].size > d->ht[0].used) ?
d->ht[0].size : d->ht[0].used)*2);
+ }
return DICT_OK;
}
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "intset.h"
+#include "zmalloc.h"
+
+/* Note that these encodings are ordered, so:
+ * INTSET_ENC_INT16 < INTSET_ENC_INT32 < INTSET_ENC_INT64. */
+#define INTSET_ENC_INT16 (sizeof(int16_t))
+#define INTSET_ENC_INT32 (sizeof(int32_t))
+#define INTSET_ENC_INT64 (sizeof(int64_t))
+
+/* Return the required encoding for the provided value. */
+static uint8_t _intsetValueEncoding(int64_t v) {
+ if (v < INT32_MIN || v > INT32_MAX)
+ return INTSET_ENC_INT64;
+ else if (v < INT16_MIN || v > INT16_MAX)
+ return INTSET_ENC_INT32;
+ return INTSET_ENC_INT16;
+}
+
+/* Return the value at pos, given an encoding. */
+static int64_t _intsetGetEncoded(intset *is, int pos, uint8_t enc) {
+ if (enc == INTSET_ENC_INT64)
+ return ((int64_t*)is->contents)[pos];
+ else if (enc == INTSET_ENC_INT32)
+ return ((int32_t*)is->contents)[pos];
+ return ((int16_t*)is->contents)[pos];
+}
+
+/* Return the value at pos, using the configured encoding. */
+static int64_t _intsetGet(intset *is, int pos) {
+ return _intsetGetEncoded(is,pos,is->encoding);
+}
+
+/* Set the value at pos, using the configured encoding. */
+static void _intsetSet(intset *is, int pos, int64_t value) {
+ if (is->encoding == INTSET_ENC_INT64)
+ ((int64_t*)is->contents)[pos] = value;
+ else if (is->encoding == INTSET_ENC_INT32)
+ ((int32_t*)is->contents)[pos] = value;
+ else
+ ((int16_t*)is->contents)[pos] = value;
+}
+
+/* Create an empty intset. */
+intset *intsetNew(void) {
+ intset *is = zmalloc(sizeof(intset));
+ is->encoding = INTSET_ENC_INT16;
+ is->length = 0;
+ return is;
+}
+
+/* Resize the intset */
+static intset *intsetResize(intset *is, uint32_t len) {
+ uint32_t size = len*is->encoding;
+ is = zrealloc(is,sizeof(intset)+size);
+ return is;
+}
+
+/* Search for the position of "value". Return 1 when the value was found and
+ * sets "pos" to the position of the value within the intset. Return 0 when
+ * the value is not present in the intset and sets "pos" to the position
+ * where "value" can be inserted. */
+static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
+ int min = 0, max = is->length-1, mid = -1;
+ int64_t cur = -1;
+
+ /* The value can never be found when the set is empty */
+ if (is->length == 0) {
+ if (pos) *pos = 0;
+ return 0;
+ } else {
+ /* Check for the case where we know we cannot find the value,
+ * but do know the insert position. */
+ if (value > _intsetGet(is,is->length-1)) {
+ if (pos) *pos = is->length;
+ return 0;
+ } else if (value < _intsetGet(is,0)) {
+ if (pos) *pos = 0;
+ return 0;
+ }
+ }
+
+ while(max >= min) {
+ mid = (min+max)/2;
+ cur = _intsetGet(is,mid);
+ if (value > cur) {
+ min = mid+1;
+ } else if (value < cur) {
+ max = mid-1;
+ } else {
+ break;
+ }
+ }
+
+ if (value == cur) {
+ if (pos) *pos = mid;
+ return 1;
+ } else {
+ if (pos) *pos = min;
+ return 0;
+ }
+}
+
+/* Upgrades the intset to a larger encoding and inserts the given integer. */
+static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {
+ uint8_t curenc = is->encoding;
+ uint8_t newenc = _intsetValueEncoding(value);
+ int length = is->length;
+ int prepend = value < 0 ? 1 : 0;
+
+ /* First set new encoding and resize */
+ is->encoding = newenc;
+ is = intsetResize(is,is->length+1);
+
+ /* Upgrade back-to-front so we don't overwrite values.
+ * Note that the "prepend" variable is used to make sure we have an empty
+ * space at either the beginning or the end of the intset. */
+ while(length--)
+ _intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));
+
+ /* Set the value at the beginning or the end. */
+ if (prepend)
+ _intsetSet(is,0,value);
+ else
+ _intsetSet(is,is->length,value);
+ is->length++;
+ return is;
+}
+
+static void intsetMoveTail(intset *is, uint32_t from, uint32_t to) {
+ void *src, *dst;
+ uint32_t bytes = is->length-from;
+ if (is->encoding == INTSET_ENC_INT64) {
+ src = (int64_t*)is->contents+from;
+ dst = (int64_t*)is->contents+to;
+ bytes *= sizeof(int64_t);
+ } else if (is->encoding == INTSET_ENC_INT32) {
+ src = (int32_t*)is->contents+from;
+ dst = (int32_t*)is->contents+to;
+ bytes *= sizeof(int32_t);
+ } else {
+ src = (int16_t*)is->contents+from;
+ dst = (int16_t*)is->contents+to;
+ bytes *= sizeof(int16_t);
+ }
+ memmove(dst,src,bytes);
+}
+
+/* Insert an integer in the intset */
+intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {
+ uint8_t valenc = _intsetValueEncoding(value);
+ uint32_t pos;
+ if (success) *success = 1;
+
+ /* Upgrade encoding if necessary. If we need to upgrade, we know that
+ * this value should be either appended (if > 0) or prepended (if < 0),
+ * because it lies outside the range of existing values. */
+ if (valenc > is->encoding) {
+ /* This always succeeds, so we don't need to curry *success. */
+ return intsetUpgradeAndAdd(is,value);
+ } else {
+ /* Abort if the value is already present in the set.
+ * This call will populate "pos" with the right position to insert
+ * the value when it cannot be found. */
+ if (intsetSearch(is,value,&pos)) {
+ if (success) *success = 0;
+ return is;
+ }
+
+ is = intsetResize(is,is->length+1);
+ if (pos < is->length) intsetMoveTail(is,pos,pos+1);
+ }
+
+ _intsetSet(is,pos,value);
+ is->length++;
+ return is;
+}
+
+/* Delete integer from intset */
+intset *intsetRemove(intset *is, int64_t value, uint8_t *success) {
+ uint8_t valenc = _intsetValueEncoding(value);
+ uint32_t pos;
+ if (success) *success = 0;
+
+ if (valenc <= is->encoding && intsetSearch(is,value,&pos)) {
+ /* We know we can delete */
+ if (success) *success = 1;
+
+ /* Overwrite value with tail and update length */
+ if (pos < (is->length-1)) intsetMoveTail(is,pos+1,pos);
+ is = intsetResize(is,is->length-1);
+ is->length--;
+ }
+ return is;
+}
+
+/* Determine whether a value belongs to this set */
+uint8_t intsetFind(intset *is, int64_t value) {
+ uint8_t valenc = _intsetValueEncoding(value);
+ return valenc <= is->encoding && intsetSearch(is,value,NULL);
+}
+
+/* Return random member */
+int64_t intsetRandom(intset *is) {
+ return _intsetGet(is,rand()%is->length);
+}
+
+/* Sets the value to the value at the given position. When this position is
+ * out of range the function returns 0, when in range it returns 1. */
+uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value) {
+ if (pos < is->length) {
+ *value = _intsetGet(is,pos);
+ return 1;
+ }
+ return 0;
+}
+
+/* Return intset length */
+uint32_t intsetLen(intset *is) {
+ return is->length;
+}
+
+#ifdef INTSET_TEST_MAIN
+#include <sys/time.h>
+
+void intsetRepr(intset *is) {
+ int i;
+ for (i = 0; i < is->length; i++) {
+ printf("%lld\n", (uint64_t)_intsetGet(is,i));
+ }
+ printf("\n");
+}
+
+void error(char *err) {
+ printf("%s\n", err);
+ exit(1);
+}
+
+void ok(void) {
+ printf("OK\n");
+}
+
+long long usec(void) {
+ struct timeval tv;
+ gettimeofday(&tv,NULL);
+ return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
+}
+
+#define assert(_e) ((_e)?(void)0:(_assert(#_e,__FILE__,__LINE__),exit(1)))
+void _assert(char *estr, char *file, int line) {
+ printf("\n\n=== ASSERTION FAILED ===\n");
+ printf("==> %s:%d '%s' is not true\n",file,line,estr);
+}
+
+intset *createSet(int bits, int size) {
+ uint64_t mask = (1<<bits)-1;
+ uint64_t i, value;
+ intset *is = intsetNew();
+
+ for (i = 0; i < size; i++) {
+ if (bits > 32) {
+ value = (rand()*rand()) & mask;
+ } else {
+ value = rand() & mask;
+ }
+ is = intsetAdd(is,value,NULL);
+ }
+ return is;
+}
+
+void checkConsistency(intset *is) {
+ int i;
+
+ for (i = 0; i < (is->length-1); i++) {
+ if (is->encoding == INTSET_ENC_INT16) {
+ int16_t *i16 = (int16_t*)is->contents;
+ assert(i16[i] < i16[i+1]);
+ } else if (is->encoding == INTSET_ENC_INT32) {
+ int32_t *i32 = (int32_t*)is->contents;
+ assert(i32[i] < i32[i+1]);
+ } else {
+ int64_t *i64 = (int64_t*)is->contents;
+ assert(i64[i] < i64[i+1]);
+ }
+ }
+}
+
+int main(int argc, char **argv) {
+ uint8_t success;
+ int i;
+ intset *is;
+ sranddev();
+
+ printf("Value encodings: "); {
+ assert(_intsetValueEncoding(-32768) == INTSET_ENC_INT16);
+ assert(_intsetValueEncoding(+32767) == INTSET_ENC_INT16);
+ assert(_intsetValueEncoding(-32769) == INTSET_ENC_INT32);
+ assert(_intsetValueEncoding(+32768) == INTSET_ENC_INT32);
+ assert(_intsetValueEncoding(-2147483648) == INTSET_ENC_INT32);
+ assert(_intsetValueEncoding(+2147483647) == INTSET_ENC_INT32);
+ assert(_intsetValueEncoding(-2147483649) == INTSET_ENC_INT64);
+ assert(_intsetValueEncoding(+2147483648) == INTSET_ENC_INT64);
+ assert(_intsetValueEncoding(-9223372036854775808ull) == INTSET_ENC_INT64);
+ assert(_intsetValueEncoding(+9223372036854775807ull) == INTSET_ENC_INT64);
+ ok();
+ }
+
+ printf("Basic adding: "); {
+ is = intsetNew();
+ is = intsetAdd(is,5,&success); assert(success);
+ is = intsetAdd(is,6,&success); assert(success);
+ is = intsetAdd(is,4,&success); assert(success);
+ is = intsetAdd(is,4,&success); assert(!success);
+ ok();
+ }
+
+ printf("Large number of random adds: "); {
+ int inserts = 0;
+ is = intsetNew();
+ for (i = 0; i < 1024; i++) {
+ is = intsetAdd(is,rand()%0x800,&success);
+ if (success) inserts++;
+ }
+ assert(is->length == inserts);
+ checkConsistency(is);
+ ok();
+ }
+
+ printf("Upgrade from int16 to int32: "); {
+ is = intsetNew();
+ is = intsetAdd(is,32,NULL);
+ assert(is->encoding == INTSET_ENC_INT16);
+ is = intsetAdd(is,65535,NULL);
+ assert(is->encoding == INTSET_ENC_INT32);
+ assert(intsetFind(is,32));
+ assert(intsetFind(is,65535));
+ checkConsistency(is);
+
+ is = intsetNew();
+ is = intsetAdd(is,32,NULL);
+ assert(is->encoding == INTSET_ENC_INT16);
+ is = intsetAdd(is,-65535,NULL);
+ assert(is->encoding == INTSET_ENC_INT32);
+ assert(intsetFind(is,32));
+ assert(intsetFind(is,-65535));
+ checkConsistency(is);
+ ok();
+ }
+
+ printf("Upgrade from int16 to int64: "); {
+ is = intsetNew();
+ is = intsetAdd(is,32,NULL);
+ assert(is->encoding == INTSET_ENC_INT16);
+ is = intsetAdd(is,4294967295,NULL);
+ assert(is->encoding == INTSET_ENC_INT64);
+ assert(intsetFind(is,32));
+ assert(intsetFind(is,4294967295));
+ checkConsistency(is);
+
+ is = intsetNew();
+ is = intsetAdd(is,32,NULL);
+ assert(is->encoding == INTSET_ENC_INT16);
+ is = intsetAdd(is,-4294967295,NULL);
+ assert(is->encoding == INTSET_ENC_INT64);
+ assert(intsetFind(is,32));
+ assert(intsetFind(is,-4294967295));
+ checkConsistency(is);
+ ok();
+ }
+
+ printf("Upgrade from int32 to int64: "); {
+ is = intsetNew();
+ is = intsetAdd(is,65535,NULL);
+ assert(is->encoding == INTSET_ENC_INT32);
+ is = intsetAdd(is,4294967295,NULL);
+ assert(is->encoding == INTSET_ENC_INT64);
+ assert(intsetFind(is,65535));
+ assert(intsetFind(is,4294967295));
+ checkConsistency(is);
+
+ is = intsetNew();
+ is = intsetAdd(is,65535,NULL);
+ assert(is->encoding == INTSET_ENC_INT32);
+ is = intsetAdd(is,-4294967295,NULL);
+ assert(is->encoding == INTSET_ENC_INT64);
+ assert(intsetFind(is,65535));
+ assert(intsetFind(is,-4294967295));
+ checkConsistency(is);
+ ok();
+ }
+
+ printf("Stress lookups: "); {
+ long num = 100000, size = 10000;
+ int i, bits = 20;
+ long long start;
+ is = createSet(bits,size);
+ checkConsistency(is);
+
+ start = usec();
+ for (i = 0; i < num; i++) intsetSearch(is,rand() % ((1<<bits)-1),NULL);
+ printf("%ld lookups, %ld element set, %lldusec\n",num,size,usec()-start);
+ }
+
+ printf("Stress add+delete: "); {
+ int i, v1, v2;
+ is = intsetNew();
+ for (i = 0; i < 0xffff; i++) {
+ v1 = rand() % 0xfff;
+ is = intsetAdd(is,v1,NULL);
+ assert(intsetFind(is,v1));
+
+ v2 = rand() % 0xfff;
+ is = intsetRemove(is,v2,NULL);
+ assert(!intsetFind(is,v2));
+ }
+ checkConsistency(is);
+ ok();
+ }
+}
+#endif
--- /dev/null
+#ifndef __INTSET_H
+#define __INTSET_H
+#include <stdint.h>
+
+typedef struct intset {
+ uint32_t encoding;
+ uint32_t length;
+ int8_t contents[];
+} intset;
+
+intset *intsetNew(void);
+intset *intsetAdd(intset *is, int64_t value, uint8_t *success);
+intset *intsetRemove(intset *is, int64_t value, uint8_t *success);
+uint8_t intsetFind(intset *is, int64_t value);
+int64_t intsetRandom(intset *is);
+uint8_t intsetGet(intset *is, uint32_t pos, int64_t *value);
+uint32_t intsetLen(intset *is);
+
+#endif // __INTSET_H
void multiCommand(redisClient *c) {
if (c->flags & REDIS_MULTI) {
- addReplySds(c,sdsnew("-ERR MULTI calls can not be nested\r\n"));
+ addReplyError(c,"MULTI calls can not be nested");
return;
}
c->flags |= REDIS_MULTI;
void discardCommand(redisClient *c) {
if (!(c->flags & REDIS_MULTI)) {
- addReplySds(c,sdsnew("-ERR DISCARD without MULTI\r\n"));
+ addReplyError(c,"DISCARD without MULTI");
return;
}
int orig_argc;
if (!(c->flags & REDIS_MULTI)) {
- addReplySds(c,sdsnew("-ERR EXEC without MULTI\r\n"));
+ addReplyError(c,"EXEC without MULTI");
return;
}
unwatchAllKeys(c); /* Unwatch ASAP otherwise we'll waste CPU cycles */
orig_argv = c->argv;
orig_argc = c->argc;
- addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->mstate.count));
+ addReplyMultiBulkLen(c,c->mstate.count);
for (j = 0; j < c->mstate.count; j++) {
c->argc = c->mstate.commands[j].argc;
c->argv = c->mstate.commands[j].argv;
int j;
if (c->flags & REDIS_MULTI) {
- addReplySds(c,sdsnew("-ERR WATCH inside MULTI is not allowed\r\n"));
+ addReplyError(c,"WATCH inside MULTI is not allowed");
return;
}
for (j = 1; j < c->argc; j++)
#include "redis.h"
-
#include <sys/uio.h>
void *dupClientReplyValue(void *o) {
}
redisClient *createClient(int fd) {
- redisClient *c = zmalloc(sizeof(*c));
+ redisClient *c = zmalloc(sizeof(redisClient));
+ c->bufpos = 0;
anetNonBlock(NULL,fd);
anetTcpNoDelay(NULL,fd);
if (!c) return NULL;
+ if (aeCreateFileEvent(server.el,fd,AE_READABLE,
+ readQueryFromClient, c) == AE_ERR)
+ {
+ close(fd);
+ zfree(c);
+ return NULL;
+ }
+
selectDb(c,0);
c->fd = fd;
c->querybuf = sdsempty();
+ c->newline = NULL;
c->argc = 0;
c->argv = NULL;
c->bulklen = -1;
c->pubsub_patterns = listCreate();
listSetFreeMethod(c->pubsub_patterns,decrRefCount);
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
- if (aeCreateFileEvent(server.el, c->fd, AE_READABLE,
- readQueryFromClient, c) == AE_ERR) {
- freeClient(c);
- return NULL;
- }
listAddNodeTail(server.clients,c);
initClientMultiState(c);
return c;
}
-void addReply(redisClient *c, robj *obj) {
- if (listLength(c->reply) == 0 &&
+int _installWriteEvent(redisClient *c) {
+ if (c->fd <= 0) return REDIS_ERR;
+ if (c->bufpos == 0 && listLength(c->reply) == 0 &&
(c->replstate == REDIS_REPL_NONE ||
c->replstate == REDIS_REPL_ONLINE) &&
aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
- sendReplyToClient, c) == AE_ERR) return;
+ sendReplyToClient, c) == AE_ERR) return REDIS_ERR;
+ return REDIS_OK;
+}
- if (server.vm_enabled && obj->storage != REDIS_VM_MEMORY) {
- obj = dupStringObject(obj);
- obj->refcount = 0; /* getDecodedObject() will increment the refcount */
+/* Create a duplicate of the last object in the reply list when
+ * it is not exclusively owned by the reply list. */
+robj *dupLastObjectIfNeeded(list *reply) {
+ robj *new, *cur;
+ listNode *ln;
+ redisAssert(listLength(reply) > 0);
+ ln = listLast(reply);
+ cur = listNodeValue(ln);
+ if (cur->refcount > 1) {
+ new = dupStringObject(cur);
+ decrRefCount(cur);
+ listNodeValue(ln) = new;
}
- listAddNodeTail(c->reply,getDecodedObject(obj));
+ return listNodeValue(ln);
}
-void addReplySds(redisClient *c, sds s) {
- robj *o = createObject(REDIS_STRING,s);
- addReply(c,o);
- decrRefCount(o);
+int _addReplyToBuffer(redisClient *c, char *s, size_t len) {
+ size_t available = sizeof(c->buf)-c->bufpos;
+
+ /* If there already are entries in the reply list, we cannot
+ * add anything more to the static buffer. */
+ if (listLength(c->reply) > 0) return REDIS_ERR;
+
+ /* Check that the buffer has enough space available for this string. */
+ if (len > available) return REDIS_ERR;
+
+ memcpy(c->buf+c->bufpos,s,len);
+ c->bufpos+=len;
+ return REDIS_OK;
}
-void addReplyDouble(redisClient *c, double d) {
- char buf[128];
+void _addReplyObjectToList(redisClient *c, robj *o) {
+ robj *tail;
+ if (listLength(c->reply) == 0) {
+ incrRefCount(o);
+ listAddNodeTail(c->reply,o);
+ } else {
+ tail = listNodeValue(listLast(c->reply));
- snprintf(buf,sizeof(buf),"%.17g",d);
- addReplySds(c,sdscatprintf(sdsempty(),"$%lu\r\n%s\r\n",
- (unsigned long) strlen(buf),buf));
+ /* Append to this object when possible. */
+ if (tail->ptr != NULL &&
+ sdslen(tail->ptr)+sdslen(o->ptr) <= REDIS_REPLY_CHUNK_BYTES)
+ {
+ tail = dupLastObjectIfNeeded(c->reply);
+ tail->ptr = sdscatlen(tail->ptr,o->ptr,sdslen(o->ptr));
+ } else {
+ incrRefCount(o);
+ listAddNodeTail(c->reply,o);
+ }
+ }
}
-void addReplyLongLong(redisClient *c, long long ll) {
- char buf[128];
- size_t len;
+/* This method takes responsibility over the sds. When it is no longer
+ * needed it will be free'd, otherwise it ends up in a robj. */
+void _addReplySdsToList(redisClient *c, sds s) {
+ robj *tail;
+ if (listLength(c->reply) == 0) {
+ listAddNodeTail(c->reply,createObject(REDIS_STRING,s));
+ } else {
+ tail = listNodeValue(listLast(c->reply));
- if (ll == 0) {
- addReply(c,shared.czero);
- return;
- } else if (ll == 1) {
- addReply(c,shared.cone);
+ /* Append to this object when possible. */
+ if (tail->ptr != NULL &&
+ sdslen(tail->ptr)+sdslen(s) <= REDIS_REPLY_CHUNK_BYTES)
+ {
+ tail = dupLastObjectIfNeeded(c->reply);
+ tail->ptr = sdscatlen(tail->ptr,s,sdslen(s));
+ sdsfree(s);
+ } else {
+ listAddNodeTail(c->reply,createObject(REDIS_STRING,s));
+ }
+ }
+}
+
+void _addReplyStringToList(redisClient *c, char *s, size_t len) {
+ robj *tail;
+ if (listLength(c->reply) == 0) {
+ listAddNodeTail(c->reply,createStringObject(s,len));
+ } else {
+ tail = listNodeValue(listLast(c->reply));
+
+ /* Append to this object when possible. */
+ if (tail->ptr != NULL &&
+ sdslen(tail->ptr)+len <= REDIS_REPLY_CHUNK_BYTES)
+ {
+ tail = dupLastObjectIfNeeded(c->reply);
+ tail->ptr = sdscatlen(tail->ptr,s,len);
+ } else {
+ listAddNodeTail(c->reply,createStringObject(s,len));
+ }
+ }
+}
+
+void addReply(redisClient *c, robj *obj) {
+ if (_installWriteEvent(c) != REDIS_OK) return;
+ redisAssert(!server.vm_enabled || obj->storage == REDIS_VM_MEMORY);
+
+ /* This is an important place where we can avoid copy-on-write
+ * when there is a saving child running, avoiding touching the
+ * refcount field of the object if it's not needed.
+ *
+ * If the encoding is RAW and there is room in the static buffer
+ * we'll be able to send the object to the client without
+ * messing with its page. */
+ if (obj->encoding == REDIS_ENCODING_RAW) {
+ if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
+ _addReplyObjectToList(c,obj);
+ } else {
+ obj = getDecodedObject(obj);
+ if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)
+ _addReplyObjectToList(c,obj);
+ decrRefCount(obj);
+ }
+}
+
+void addReplySds(redisClient *c, sds s) {
+ if (_installWriteEvent(c) != REDIS_OK) {
+ /* The caller expects the sds to be free'd. */
+ sdsfree(s);
return;
}
- buf[0] = ':';
+ if (_addReplyToBuffer(c,s,sdslen(s)) == REDIS_OK) {
+ sdsfree(s);
+ } else {
+ /* This method free's the sds when it is no longer needed. */
+ _addReplySdsToList(c,s);
+ }
+}
+
+void addReplyString(redisClient *c, char *s, size_t len) {
+ if (_installWriteEvent(c) != REDIS_OK) return;
+ if (_addReplyToBuffer(c,s,len) != REDIS_OK)
+ _addReplyStringToList(c,s,len);
+}
+
+void _addReplyError(redisClient *c, char *s, size_t len) {
+ addReplyString(c,"-ERR ",5);
+ addReplyString(c,s,len);
+ addReplyString(c,"\r\n",2);
+}
+
+void addReplyError(redisClient *c, char *err) {
+ _addReplyError(c,err,strlen(err));
+}
+
+void addReplyErrorFormat(redisClient *c, const char *fmt, ...) {
+ va_list ap;
+ va_start(ap,fmt);
+ sds s = sdscatvprintf(sdsempty(),fmt,ap);
+ va_end(ap);
+ _addReplyError(c,s,sdslen(s));
+ sdsfree(s);
+}
+
+void _addReplyStatus(redisClient *c, char *s, size_t len) {
+ addReplyString(c,"+",1);
+ addReplyString(c,s,len);
+ addReplyString(c,"\r\n",2);
+}
+
+void addReplyStatus(redisClient *c, char *status) {
+ _addReplyStatus(c,status,strlen(status));
+}
+
+void addReplyStatusFormat(redisClient *c, const char *fmt, ...) {
+ va_list ap;
+ va_start(ap,fmt);
+ sds s = sdscatvprintf(sdsempty(),fmt,ap);
+ va_end(ap);
+ _addReplyStatus(c,s,sdslen(s));
+ sdsfree(s);
+}
+
+/* Adds an empty object to the reply list that will contain the multi bulk
+ * length, which is not known when this function is called. */
+void *addDeferredMultiBulkLength(redisClient *c) {
+ /* Note that we install the write event here even if the object is not
+ * ready to be sent, since we are sure that before returning to the
+ * event loop setDeferredMultiBulkLength() will be called. */
+ if (_installWriteEvent(c) != REDIS_OK) return NULL;
+ listAddNodeTail(c->reply,createObject(REDIS_STRING,NULL));
+ return listLast(c->reply);
+}
+
+/* Populate the length object and try glueing it to the next chunk. */
+void setDeferredMultiBulkLength(redisClient *c, void *node, long length) {
+ listNode *ln = (listNode*)node;
+ robj *len, *next;
+
+ /* Abort when *node is NULL (see addDeferredMultiBulkLength). */
+ if (node == NULL) return;
+
+ len = listNodeValue(ln);
+ len->ptr = sdscatprintf(sdsempty(),"*%ld\r\n",length);
+ if (ln->next != NULL) {
+ next = listNodeValue(ln->next);
+
+ /* Only glue when the next node is non-NULL (an sds in this case) */
+ if (next->ptr != NULL) {
+ len->ptr = sdscatlen(len->ptr,next->ptr,sdslen(next->ptr));
+ listDelNode(c->reply,ln->next);
+ }
+ }
+}
+
+void addReplyDouble(redisClient *c, double d) {
+ char dbuf[128], sbuf[128];
+ int dlen, slen;
+ dlen = snprintf(dbuf,sizeof(dbuf),"%.17g",d);
+ slen = snprintf(sbuf,sizeof(sbuf),"$%d\r\n%s\r\n",dlen,dbuf);
+ addReplyString(c,sbuf,slen);
+}
+
+void _addReplyLongLong(redisClient *c, long long ll, char prefix) {
+ char buf[128];
+ int len;
+ buf[0] = prefix;
len = ll2string(buf+1,sizeof(buf)-1,ll);
buf[len+1] = '\r';
buf[len+2] = '\n';
- addReplySds(c,sdsnewlen(buf,len+3));
+ addReplyString(c,buf,len+3);
}
-void addReplyUlong(redisClient *c, unsigned long ul) {
- char buf[128];
- size_t len;
+void addReplyLongLong(redisClient *c, long long ll) {
+ _addReplyLongLong(c,ll,':');
+}
- if (ul == 0) {
- addReply(c,shared.czero);
- return;
- } else if (ul == 1) {
- addReply(c,shared.cone);
- return;
- }
- len = snprintf(buf,sizeof(buf),":%lu\r\n",ul);
- addReplySds(c,sdsnewlen(buf,len));
+void addReplyMultiBulkLen(redisClient *c, long length) {
+ _addReplyLongLong(c,length,'*');
}
void addReplyBulkLen(redisClient *c, robj *obj) {
- size_t len, intlen;
- char buf[128];
+ size_t len;
if (obj->encoding == REDIS_ENCODING_RAW) {
len = sdslen(obj->ptr);
len++;
}
}
- buf[0] = '$';
- intlen = ll2string(buf+1,sizeof(buf)-1,(long long)len);
- buf[intlen+1] = '\r';
- buf[intlen+2] = '\n';
- addReplySds(c,sdsnewlen(buf,intlen+3));
+ _addReplyLongLong(c,len,'$');
}
void addReplyBulk(redisClient *c, robj *obj) {
server.vm_blocked_clients--;
}
listRelease(c->io_keys);
- /* Master/slave cleanup */
+ /* Master/slave cleanup.
+ * Case 1: we lost the connection with a slave. */
if (c->flags & REDIS_SLAVE) {
if (c->replstate == REDIS_REPL_SEND_BULK && c->repldbfd != -1)
close(c->repldbfd);
redisAssert(ln != NULL);
listDelNode(l,ln);
}
+
+ /* Case 2: we lost the connection with the master. */
if (c->flags & REDIS_MASTER) {
server.master = NULL;
server.replstate = REDIS_REPL_CONNECT;
+ /* Since we lost the connection with the master, we should also
+ * close the connection with all our slaves if we have any, so
+ * when we'll resync with the master the other slaves will sync again
+ * with us as well. Note that also when the slave is not connected
+ * to the master it will keep refusing connections by other slaves. */
+ while (listLength(server.slaves)) {
+ ln = listFirst(server.slaves);
+ freeClient((redisClient*)ln->value);
+ }
}
/* Release memory */
zfree(c->argv);
zfree(c);
}
-#define GLUEREPLY_UP_TO (1024)
-static void glueReplyBuffersIfNeeded(redisClient *c) {
- int copylen = 0;
- char buf[GLUEREPLY_UP_TO];
- listNode *ln;
- listIter li;
- robj *o;
-
- listRewind(c->reply,&li);
- while((ln = listNext(&li))) {
- int objlen;
-
- o = ln->value;
- objlen = sdslen(o->ptr);
- if (copylen + objlen <= GLUEREPLY_UP_TO) {
- memcpy(buf+copylen,o->ptr,objlen);
- copylen += objlen;
- listDelNode(c->reply,ln);
- } else {
- if (copylen == 0) return;
- break;
- }
- }
- /* Now the output buffer is empty, add the new single element */
- o = createObject(REDIS_STRING,sdsnewlen(buf,copylen));
- listAddNodeHead(c->reply,o);
-}
-
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
redisClient *c = privdata;
int nwritten = 0, totwritten = 0, objlen;
return;
}
- while(listLength(c->reply)) {
- if (server.glueoutputbuf && listLength(c->reply) > 1)
- glueReplyBuffersIfNeeded(c);
+ while(c->bufpos > 0 || listLength(c->reply)) {
+ if (c->bufpos > 0) {
+ if (c->flags & REDIS_MASTER) {
+ /* Don't reply to a master */
+ nwritten = c->bufpos - c->sentlen;
+ } else {
+ nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);
+ if (nwritten <= 0) break;
+ }
+ c->sentlen += nwritten;
+ totwritten += nwritten;
+
+ /* If the buffer was sent, set bufpos to zero to continue with
+ * the remainder of the reply. */
+ if (c->sentlen == c->bufpos) {
+ c->bufpos = 0;
+ c->sentlen = 0;
+ }
+ } else {
+ o = listNodeValue(listFirst(c->reply));
+ objlen = sdslen(o->ptr);
- o = listNodeValue(listFirst(c->reply));
- objlen = sdslen(o->ptr);
+ if (objlen == 0) {
+ listDelNode(c->reply,listFirst(c->reply));
+ continue;
+ }
- if (objlen == 0) {
- listDelNode(c->reply,listFirst(c->reply));
- continue;
- }
+ if (c->flags & REDIS_MASTER) {
+ /* Don't reply to a master */
+ nwritten = objlen - c->sentlen;
+ } else {
+ nwritten = write(fd, ((char*)o->ptr)+c->sentlen,objlen-c->sentlen);
+ if (nwritten <= 0) break;
+ }
+ c->sentlen += nwritten;
+ totwritten += nwritten;
- if (c->flags & REDIS_MASTER) {
- /* Don't reply to a master */
- nwritten = objlen - c->sentlen;
- } else {
- nwritten = write(fd, ((char*)o->ptr)+c->sentlen, objlen - c->sentlen);
- if (nwritten <= 0) break;
- }
- c->sentlen += nwritten;
- totwritten += nwritten;
- /* If we fully sent the object on head go to the next one */
- if (c->sentlen == objlen) {
- listDelNode(c->reply,listFirst(c->reply));
- c->sentlen = 0;
+ /* If we fully sent the object on head go to the next one */
+ if (c->sentlen == objlen) {
+ listDelNode(c->reply,listFirst(c->reply));
+ c->sentlen = 0;
+ }
}
/* Note that we avoid to send more thank REDIS_MAX_WRITE_PER_EVENT
* bytes, in a single threaded server it's a good idea to serve
freeClientArgv(c);
c->bulklen = -1;
c->multibulk = 0;
+ c->newline = NULL;
}
void closeTimedoutClients(void) {
if (server.maxidletime &&
!(c->flags & REDIS_SLAVE) && /* no timeout for slaves */
!(c->flags & REDIS_MASTER) && /* no timeout for masters */
+ !(c->flags & REDIS_BLOCKED) && /* no timeout for BLPOP */
dictSize(c->pubsub_channels) == 0 && /* no timeout for pubsub */
listLength(c->pubsub_patterns) == 0 &&
(now - c->lastinteraction > server.maxidletime))
}
void processInputBuffer(redisClient *c) {
+ int seeknewline = 0;
+
again:
/* Before to process the input buffer, make sure the client is not
* waitig for a blocking operation such as BLPOP. Note that the first
* in the input buffer the client may be blocked, and the "goto again"
* will try to reiterate. The following line will make it return asap. */
if (c->flags & REDIS_BLOCKED || c->flags & REDIS_IO_WAIT) return;
+
+ if (seeknewline && c->bulklen == -1) c->newline = strchr(c->querybuf,'\n');
+ seeknewline = 1;
if (c->bulklen == -1) {
/* Read the first line of the query */
- char *p = strchr(c->querybuf,'\n');
size_t querylen;
- if (p) {
+ if (c->newline) {
+ char *p = c->newline;
sds query, *argv;
int argc, j;
+ c->newline = NULL;
query = c->querybuf;
c->querybuf = sdsempty();
querylen = 1+(p-(query));
return;
}
if (nread) {
+ size_t oldlen = sdslen(c->querybuf);
c->querybuf = sdscatlen(c->querybuf, buf, nread);
c->lastinteraction = time(NULL);
+ /* Scan this new piece of the query for the newline. We do this
+ * here in order to make sure we perform this scan just one time
+ * per piece of buffer, leading to an O(N) scan instead of O(N*N) */
+ if (c->bulklen == -1 && c->newline == NULL)
+ c->newline = strchr(c->querybuf+oldlen,'\n');
} else {
return;
}
robj *createSetObject(void) {
dict *d = dictCreate(&setDictType,NULL);
- return createObject(REDIS_SET,d);
+ robj *o = createObject(REDIS_SET,d);
+ o->encoding = REDIS_ENCODING_HT;
+ return o;
+}
+
+robj *createIntsetObject(void) {
+ intset *is = intsetNew();
+ robj *o = createObject(REDIS_SET,is);
+ o->encoding = REDIS_ENCODING_INTSET;
+ return o;
}
robj *createHashObject(void) {
}
void freeSetObject(robj *o) {
- dictRelease((dict*) o->ptr);
+ switch (o->encoding) {
+ case REDIS_ENCODING_HT:
+ dictRelease((dict*) o->ptr);
+ break;
+ case REDIS_ENCODING_INTSET:
+ zfree(o->ptr);
+ break;
+ default:
+ redisPanic("Unknown set encoding type");
+ }
}
void freeZsetObject(robj *o) {
double value;
if (getDoubleFromObject(o, &value) != REDIS_OK) {
if (msg != NULL) {
- addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg));
+ addReplyError(c,(char*)msg);
} else {
- addReplySds(c, sdsnew("-ERR value is not a double\r\n"));
+ addReplyError(c,"value is not a double");
}
return REDIS_ERR;
}
if (o->encoding == REDIS_ENCODING_RAW) {
value = strtoll(o->ptr, &eptr, 10);
if (eptr[0] != '\0') return REDIS_ERR;
+ if (errno == ERANGE && (value == LLONG_MIN || value == LLONG_MAX))
+ return REDIS_ERR;
} else if (o->encoding == REDIS_ENCODING_INT) {
value = (long)o->ptr;
} else {
}
}
- *target = value;
+ if (target) *target = value;
return REDIS_OK;
}
long long value;
if (getLongLongFromObject(o, &value) != REDIS_OK) {
if (msg != NULL) {
- addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg));
+ addReplyError(c,(char*)msg);
} else {
- addReplySds(c, sdsnew("-ERR value is not an integer\r\n"));
+ addReplyError(c,"value is not an integer or out of range");
}
return REDIS_ERR;
}
if (getLongLongFromObjectOrReply(c, o, &value, msg) != REDIS_OK) return REDIS_ERR;
if (value < LONG_MIN || value > LONG_MAX) {
if (msg != NULL) {
- addReplySds(c, sdscatprintf(sdsempty(), "-ERR %s\r\n", msg));
+ addReplyError(c,(char*)msg);
} else {
- addReplySds(c, sdsnew("-ERR value is out of range\r\n"));
+ addReplyError(c,"value is out of range");
}
return REDIS_ERR;
}
case REDIS_ENCODING_ZIPMAP: return "zipmap";
case REDIS_ENCODING_LINKEDLIST: return "linkedlist";
case REDIS_ENCODING_ZIPLIST: return "ziplist";
+ case REDIS_ENCODING_INTSET: return "intset";
default: return "unknown";
}
}
}
} else if (o->type == REDIS_SET) {
/* Save a set value */
- dict *set = o->ptr;
- dictIterator *di = dictGetIterator(set);
- dictEntry *de;
-
- if (rdbSaveLen(fp,dictSize(set)) == -1) return -1;
- while((de = dictNext(di)) != NULL) {
- robj *eleobj = dictGetEntryKey(de);
+ if (o->encoding == REDIS_ENCODING_HT) {
+ dict *set = o->ptr;
+ dictIterator *di = dictGetIterator(set);
+ dictEntry *de;
- if (rdbSaveStringObject(fp,eleobj) == -1) return -1;
+ if (rdbSaveLen(fp,dictSize(set)) == -1) return -1;
+ while((de = dictNext(di)) != NULL) {
+ robj *eleobj = dictGetEntryKey(de);
+ if (rdbSaveStringObject(fp,eleobj) == -1) return -1;
+ }
+ dictReleaseIterator(di);
+ } else if (o->encoding == REDIS_ENCODING_INTSET) {
+ intset *is = o->ptr;
+ int64_t llval;
+ int i = 0;
+
+ if (rdbSaveLen(fp,intsetLen(is)) == -1) return -1;
+ while(intsetGet(is,i++,&llval)) {
+ if (rdbSaveLongLongAsStringObject(fp,llval) == -1) return -1;
+ }
+ } else {
+ redisPanic("Unknown set encoding");
}
- dictReleaseIterator(di);
} else if (o->type == REDIS_ZSET) {
/* Save a set value */
zset *zs = o->ptr;
if (server.bgsavechildpid != -1) return REDIS_ERR;
if (server.vm_enabled) waitEmptyIOJobsQueue();
+ server.dirty_before_bgsave = server.dirty;
if ((childpid = fork()) == 0) {
/* Child */
if (server.vm_enabled) vmReopenSwapFile();
robj *rdbLoadObject(int type, FILE *fp) {
robj *o, *ele, *dec;
size_t len;
+ unsigned int i;
redisLog(REDIS_DEBUG,"LOADING OBJECT %d (at %d)\n",type,ftell(fp));
if (type == REDIS_STRING) {
} else if (type == REDIS_SET) {
/* Read list/set value */
if ((len = rdbLoadLen(fp,NULL)) == REDIS_RDB_LENERR) return NULL;
- o = createSetObject();
- /* It's faster to expand the dict to the right size asap in order
- * to avoid rehashing */
- if (len > DICT_HT_INITIAL_SIZE)
- dictExpand(o->ptr,len);
+
+ /* Use a regular set when there are too many entries. */
+ if (len > server.set_max_intset_entries) {
+ o = createSetObject();
+ /* It's faster to expand the dict to the right size asap in order
+ * to avoid rehashing */
+ if (len > DICT_HT_INITIAL_SIZE)
+ dictExpand(o->ptr,len);
+ } else {
+ o = createIntsetObject();
+ }
+
/* Load every single element of the list/set */
- while(len--) {
+ for (i = 0; i < len; i++) {
+ long long llval;
if ((ele = rdbLoadEncodedStringObject(fp)) == NULL) return NULL;
ele = tryObjectEncoding(ele);
- dictAdd((dict*)o->ptr,ele,NULL);
+
+ if (o->encoding == REDIS_ENCODING_INTSET) {
+ /* Fetch integer value from element */
+ if (isObjectRepresentableAsLongLong(ele,&llval) == REDIS_OK) {
+ o->ptr = intsetAdd(o->ptr,llval,NULL);
+ } else {
+ setTypeConvert(o,REDIS_ENCODING_HT);
+ dictExpand(o->ptr,len);
+ }
+ }
+
+ /* This will also be called when the set was just converted
+ * to regular hashtable encoded set */
+ if (o->encoding == REDIS_ENCODING_HT) {
+ dictAdd((dict*)o->ptr,ele,NULL);
+ } else {
+ decrRefCount(ele);
+ }
}
} else if (type == REDIS_ZSET) {
/* Read list/set value */
if (!bysignal && exitcode == 0) {
redisLog(REDIS_NOTICE,
"Background saving terminated with success");
- server.dirty = 0;
+ server.dirty = server.dirty - server.dirty_before_bgsave;
server.lastsave = time(NULL);
} else if (!bysignal && exitcode != 0) {
redisLog(REDIS_WARNING, "Background saving error");
long long start;
long long totlatency;
int *latency;
+ char *title;
list *clients;
int quiet;
int loop;
}
}
+/* Read a length from the buffer pointed to by *p, store the length in *len,
+ * and return the number of bytes that the cursor advanced. */
+static int readLen(char *p, int *len) {
+ char *tail = strstr(p,"\r\n");
+ if (tail == NULL)
+ return 0;
+ *tail = '\0';
+ *len = atoi(p+1);
+ return tail+2-p;
+}
+
static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask)
{
- char buf[1024];
- int nread;
+ char buf[1024], *p;
+ int nread, pos=0, len=0;
client c = privdata;
REDIS_NOTUSED(el);
REDIS_NOTUSED(fd);
REDIS_NOTUSED(mask);
- nread = read(c->fd, buf, 1024);
+ nread = read(c->fd,buf,sizeof(buf));
if (nread == -1) {
fprintf(stderr, "Reading from socket: %s\n", strerror(errno));
freeClient(c);
}
c->totreceived += nread;
c->ibuf = sdscatlen(c->ibuf,buf,nread);
+ len = sdslen(c->ibuf);
-processdata:
- /* Are we waiting for the first line of the command of for sdf
- * count in bulk or multi bulk operations? */
if (c->replytype == REPLY_INT ||
- c->replytype == REPLY_RETCODE ||
- (c->replytype == REPLY_BULK && c->readlen == -1) ||
- (c->replytype == REPLY_MBULK && c->readlen == -1) ||
- (c->replytype == REPLY_MBULK && c->mbulk == -1)) {
- char *p;
-
- /* Check if the first line is complete. This is only true if
- * there is a newline inside the buffer. */
- if ((p = strchr(c->ibuf,'\n')) != NULL) {
- if (c->replytype == REPLY_BULK ||
- (c->replytype == REPLY_MBULK && c->mbulk != -1))
- {
- /* Read the count of a bulk reply (being it a single bulk or
- * a multi bulk reply). "$<count>" for the protocol spec. */
- *p = '\0';
- *(p-1) = '\0';
- c->readlen = atoi(c->ibuf+1)+2;
- // printf("BULK ATOI: %s\n", c->ibuf+1);
- /* Handle null bulk reply "$-1" */
- if (c->readlen-2 == -1) {
- clientDone(c);
- return;
- }
- /* Leave all the rest in the input buffer */
- c->ibuf = sdsrange(c->ibuf,(p-c->ibuf)+1,-1);
- /* fall through to reach the point where the code will try
- * to check if the bulk reply is complete. */
- } else if (c->replytype == REPLY_MBULK && c->mbulk == -1) {
- /* Read the count of a multi bulk reply. That is, how many
- * bulk replies we have to read next. "*<count>" protocol. */
- *p = '\0';
- *(p-1) = '\0';
- c->mbulk = atoi(c->ibuf+1);
- /* Handle null bulk reply "*-1" */
- if (c->mbulk == -1) {
- clientDone(c);
- return;
+ c->replytype == REPLY_RETCODE)
+ {
+ /* Check if the first line is complete. This is everything we need
+ * when waiting for an integer or status code reply.*/
+ if ((p = strstr(c->ibuf,"\r\n")) != NULL)
+ goto done;
+ } else if (c->replytype == REPLY_BULK) {
+ int advance = 0;
+ if (c->readlen < 0) {
+ advance = readLen(c->ibuf+pos,&c->readlen);
+ if (advance) {
+ pos += advance;
+ if (c->readlen == -1) {
+ goto done;
+ } else {
+ /* include the trailing \r\n */
+ c->readlen += 2;
}
- // printf("%p) %d elements list\n", c, c->mbulk);
- /* Leave all the rest in the input buffer */
- c->ibuf = sdsrange(c->ibuf,(p-c->ibuf)+1,-1);
- goto processdata;
} else {
- c->ibuf = sdstrim(c->ibuf,"\r\n");
- clientDone(c);
- return;
+ goto skip;
}
}
- }
- /* bulk read, did we read everything? */
- if (((c->replytype == REPLY_MBULK && c->mbulk != -1) ||
- (c->replytype == REPLY_BULK)) && c->readlen != -1 &&
- (unsigned)c->readlen <= sdslen(c->ibuf))
- {
- // printf("BULKSTATUS mbulk:%d readlen:%d sdslen:%d\n",
- // c->mbulk,c->readlen,sdslen(c->ibuf));
- if (c->replytype == REPLY_BULK) {
- clientDone(c);
- } else if (c->replytype == REPLY_MBULK) {
- // printf("%p) %d (%d)) ",c, c->mbulk, c->readlen);
- // fwrite(c->ibuf,c->readlen,1,stdout);
- // printf("\n");
- if (--c->mbulk == 0) {
- clientDone(c);
+
+ int canconsume;
+ if (c->readlen > 0) {
+ canconsume = c->readlen > (len-pos) ? (len-pos) : c->readlen;
+ c->readlen -= canconsume;
+ pos += canconsume;
+ }
+
+ if (c->readlen == 0)
+ goto done;
+ } else if (c->replytype == REPLY_MBULK) {
+ int advance = 0;
+ if (c->mbulk == -1) {
+ advance = readLen(c->ibuf+pos,&c->mbulk);
+ if (advance) {
+ pos += advance;
+ if (c->mbulk == -1)
+ goto done;
+ } else {
+ goto skip;
+ }
+ }
+
+ int canconsume;
+ while(c->mbulk > 0 && pos < len) {
+ if (c->readlen > 0) {
+ canconsume = c->readlen > (len-pos) ? (len-pos) : c->readlen;
+ c->readlen -= canconsume;
+ pos += canconsume;
+ if (c->readlen == 0)
+ c->mbulk--;
} else {
- c->ibuf = sdsrange(c->ibuf,c->readlen,-1);
- c->readlen = -1;
- goto processdata;
+ advance = readLen(c->ibuf+pos,&c->readlen);
+ if (advance) {
+ pos += advance;
+ if (c->readlen == -1) {
+ c->mbulk--;
+ continue;
+ } else {
+ /* include the trailing \r\n */
+ c->readlen += 2;
+ }
+ } else {
+ goto skip;
+ }
}
}
+
+ if (c->mbulk == 0)
+ goto done;
}
+
+skip:
+ c->ibuf = sdsrange(c->ibuf,pos,-1);
+ return;
+done:
+ clientDone(c);
+ return;
}
static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask)
}
}
-static void showLatencyReport(char *title) {
+static void showLatencyReport(void) {
int j, seen = 0;
float perc, reqpersec;
reqpersec = (float)config.donerequests/((float)config.totlatency/1000);
if (!config.quiet) {
- printf("====== %s ======\n", title);
+ printf("====== %s ======\n", config.title);
printf(" %d requests completed in %.2f seconds\n", config.donerequests,
(float)config.totlatency/1000);
printf(" %d parallel clients\n", config.numclients);
}
printf("%.2f requests per second\n\n", reqpersec);
} else {
- printf("%s: %.2f requests per second\n", title, reqpersec);
+ printf("%s: %.2f requests per second\n", config.title, reqpersec);
}
}
-static void prepareForBenchmark(void)
-{
+static void prepareForBenchmark(char *title) {
memset(config.latency,0,sizeof(int)*(MAX_LATENCY+1));
+ config.title = title;
config.start = mstime();
config.donerequests = 0;
}
-static void endBenchmark(char *title) {
+static void endBenchmark(void) {
config.totlatency = mstime()-config.start;
- showLatencyReport(title);
+ showLatencyReport();
freeAllClients();
}
}
}
+int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData) {
+ REDIS_NOTUSED(eventLoop);
+ REDIS_NOTUSED(id);
+ REDIS_NOTUSED(clientData);
+
+ float dt = (float)(mstime()-config.start)/1000.0;
+ float rps = (float)config.donerequests/dt;
+ printf("%s: %.2f\r", config.title, rps);
+ fflush(stdout);
+ return 250; /* every 250ms */
+}
+
int main(int argc, char **argv) {
client c;
config.requests = 10000;
config.liveclients = 0;
config.el = aeCreateEventLoop();
+ aeCreateTimeEvent(config.el,1,showThroughput,NULL,NULL);
config.keepalive = 1;
config.donerequests = 0;
config.datasize = 3;
if (config.idlemode) {
printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.numclients);
- prepareForBenchmark();
+ prepareForBenchmark("IDLE");
c = createClient();
if (!c) exit(1);
c->obuf = sdsempty();
}
do {
- prepareForBenchmark();
+ prepareForBenchmark("PING");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"PING\r\n");
prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c);
aeMain(config.el);
- endBenchmark("PING");
+ endBenchmark();
- prepareForBenchmark();
+ prepareForBenchmark("PING (multi bulk)");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"*1\r\n$4\r\nPING\r\n");
prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c);
aeMain(config.el);
- endBenchmark("PING (multi bulk)");
+ endBenchmark();
- prepareForBenchmark();
+ prepareForBenchmark("SET");
c = createClient();
if (!c) exit(1);
c->obuf = sdscatprintf(c->obuf,"SET foo_rand000000000000 %d\r\n",config.datasize);
prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c);
aeMain(config.el);
- endBenchmark("SET");
+ endBenchmark();
- prepareForBenchmark();
+ prepareForBenchmark("GET");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"GET foo_rand000000000000\r\n");
prepareClientForReply(c,REPLY_BULK);
createMissingClients(c);
aeMain(config.el);
- endBenchmark("GET");
+ endBenchmark();
- prepareForBenchmark();
+ prepareForBenchmark("INCR");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"INCR counter_rand000000000000\r\n");
prepareClientForReply(c,REPLY_INT);
createMissingClients(c);
aeMain(config.el);
- endBenchmark("INCR");
+ endBenchmark();
- prepareForBenchmark();
+ prepareForBenchmark("LPUSH");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n");
prepareClientForReply(c,REPLY_INT);
createMissingClients(c);
aeMain(config.el);
- endBenchmark("LPUSH");
+ endBenchmark();
- prepareForBenchmark();
+ prepareForBenchmark("LPOP");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LPOP mylist\r\n");
prepareClientForReply(c,REPLY_BULK);
createMissingClients(c);
aeMain(config.el);
- endBenchmark("LPOP");
+ endBenchmark();
- prepareForBenchmark();
+ prepareForBenchmark("SADD");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"SADD myset 24\r\ncounter_rand000000000000\r\n");
prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c);
aeMain(config.el);
- endBenchmark("SADD");
+ endBenchmark();
- prepareForBenchmark();
+ prepareForBenchmark("SPOP");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"SPOP myset\r\n");
prepareClientForReply(c,REPLY_BULK);
createMissingClients(c);
aeMain(config.el);
- endBenchmark("SPOP");
+ endBenchmark();
- prepareForBenchmark();
+ prepareForBenchmark("LPUSH (again, in order to bench LRANGE)");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n");
prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c);
aeMain(config.el);
- endBenchmark("LPUSH (again, in order to bench LRANGE)");
+ endBenchmark();
- prepareForBenchmark();
+ prepareForBenchmark("LRANGE (first 100 elements)");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 99\r\n");
prepareClientForReply(c,REPLY_MBULK);
createMissingClients(c);
aeMain(config.el);
- endBenchmark("LRANGE (first 100 elements)");
+ endBenchmark();
- prepareForBenchmark();
+ prepareForBenchmark("LRANGE (first 300 elements)");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 299\r\n");
prepareClientForReply(c,REPLY_MBULK);
createMissingClients(c);
aeMain(config.el);
- endBenchmark("LRANGE (first 300 elements)");
+ endBenchmark();
- prepareForBenchmark();
+ prepareForBenchmark("LRANGE (first 450 elements)");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 449\r\n");
prepareClientForReply(c,REPLY_MBULK);
createMissingClients(c);
aeMain(config.el);
- endBenchmark("LRANGE (first 450 elements)");
+ endBenchmark();
- prepareForBenchmark();
+ prepareForBenchmark("LRANGE (first 600 elements)");
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LRANGE mylist 0 599\r\n");
prepareClientForReply(c,REPLY_MBULK);
createMissingClients(c);
aeMain(config.el);
- endBenchmark("LRANGE (first 600 elements)");
+ endBenchmark();
printf("\n");
} while(config.loop);
/* data type to hold offset in file and size */
typedef struct {
void *data;
- unsigned long size;
- unsigned long offset;
+ size_t size;
+ size_t offset;
} pos;
static unsigned char level = 0;
/* Hold a stack of errors */
typedef struct {
char error[16][1024];
- unsigned long offset[16];
- unsigned int level;
+ size_t offset[16];
+ size_t level;
} errors_t;
static errors_t errors;
if (p.offset + num > p.size) {
return 0;
} else {
- memcpy(target, (void*)((unsigned long)p.data + p.offset), num);
+ memcpy(target, (void*)((size_t)p.data + p.offset), num);
if (!peek) positions[level].offset += num;
}
return 1;
printf("%s %s %s\n", head, body, tail);
}
-void printValid(int ops, int bytes) {
+void printValid(uint64_t ops, uint64_t bytes) {
char body[80];
- sprintf(body, "Processed %d valid opcodes (in %d bytes)", ops, bytes);
+ sprintf(body, "Processed %llu valid opcodes (in %llu bytes)",
+ (unsigned long long) ops, (unsigned long long) bytes);
printCentered(4, 80, body);
}
-void printSkipped(int bytes, int offset) {
+void printSkipped(uint64_t bytes, uint64_t offset) {
char body[80];
- sprintf(body, "Skipped %d bytes (resuming at 0x%08x)", bytes, offset);
+ sprintf(body, "Skipped %llu bytes (resuming at 0x%08llx)",
+ (unsigned long long) bytes, (unsigned long long) offset);
printCentered(4, 80, body);
}
}
void process() {
- int i, num_errors = 0, num_valid_ops = 0, num_valid_bytes = 0;
+ uint64_t num_errors = 0, num_valid_ops = 0, num_valid_bytes = 0;
entry entry;
processHeader();
num_valid_bytes = 0;
/* search for next valid entry */
- unsigned long offset = positions[0].offset + 1;
+ uint64_t offset = positions[0].offset + 1;
+ int i = 0;
+
while (!entry.success && offset < positions[0].size) {
positions[1].offset = offset;
}
/* print summary on errors */
- if (num_errors > 0) {
+ if (num_errors) {
printf("\n");
- printf("Total unprocessable opcodes: %d\n", num_errors);
+ printf("Total unprocessable opcodes: %llu\n",
+ (unsigned long long) num_errors);
}
}
}
int fd;
- unsigned long size;
+ off_t size;
struct stat stat;
void *data;
size = stat.st_size;
}
+ if (sizeof(size_t) == sizeof(int32_t) && size >= INT_MAX) {
+ ERROR("Cannot check dump files >2GB on a 32-bit platform\n");
+ }
+
data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
ERROR("Cannot mmap: %s\n", argv[1]);
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
#include "anet.h"
#include "sds.h"
int hostport;
long repeat;
int dbnum;
- int argn_from_stdin;
int interactive;
int shutdown;
int monitor_mode;
int pubsub_mode;
- int raw_output;
+ int raw_output; /* output mode per command */
+ int tty; /* flag for default output format */
+ int stdinarg; /* get last arg from stdin. (-x option) */
+ char mb_sep;
char *auth;
char *historyfile;
} config;
static int cliReadReply(int fd);
static void usage();
-static int cliConnect(void) {
+/* Connect to the client. If force is not zero the connection is performed
+ * even if there is already a connected socket. */
+static int cliConnect(int force) {
char err[ANET_ERR_LEN];
static int fd = ANET_ERR;
- if (fd == ANET_ERR) {
+ if (fd == ANET_ERR || force) {
+ if (force) close(fd);
fd = anetTcpConnect(err,config.hostip,config.hostport);
if (fd == ANET_ERR) {
fprintf(stderr, "Could not connect to Redis at %s:%d: %s", config.hostip, config.hostport, err);
ssize_t ret;
ret = read(fd,&c,1);
- if (ret == -1) {
+ if (ret <= 0) {
sdsfree(line);
return NULL;
} else if ((ret == 0) || (c == '\n')) {
if (reply == NULL) return 1;
if (!quiet)
- printf("%s\n", reply);
+ printf("%s", reply);
sdsfree(reply);
return 0;
}
}
s++;
}
- printf("\"\n");
+ printf("\"");
}
static int cliReadBulkReply(int fd) {
reply = zmalloc(bulklen);
anetRead(fd,reply,bulklen);
anetRead(fd,crlf,2);
- if (config.raw_output || !isatty(fileno(stdout))) {
+ if (config.raw_output || !config.tty) {
if (bulklen && fwrite(reply,bulklen,1,stdout) == 0) {
zfree(reply);
return 1;
static int cliReadMultiBulkReply(int fd) {
sds replylen = cliReadLine(fd);
int elements, c = 1;
+ int retval = 0;
if (replylen == NULL) return 1;
elements = atoi(replylen);
printf("(empty list or set)\n");
}
while(elements--) {
- printf("%d. ", c);
- if (cliReadReply(fd)) return 1;
+ if (config.tty) printf("%d. ", c);
+ if (cliReadReply(fd)) retval = 1;
+ if (elements) printf("%c",config.mb_sep);
c++;
}
- return 0;
+ return retval;
}
static int cliReadReply(int fd) {
char type;
+ int nread;
- if (anetRead(fd,&type,1) <= 0) {
+ if ((nread = anetRead(fd,&type,1)) <= 0) {
if (config.shutdown) return 0;
- exit(1);
+ if (config.interactive &&
+ (nread == 0 || (nread == -1 && errno == ECONNRESET)))
+ {
+ return ECONNRESET;
+ } else {
+ printf("I/O error while reading from socket: %s",strerror(errno));
+ exit(1);
+ }
}
switch(type) {
case '-':
- printf("(error) ");
+ if (config.tty) printf("(error) ");
cliReadSingleLineReply(fd,0);
return 1;
case '+':
return cliReadSingleLineReply(fd,0);
case ':':
- printf("(integer) ");
+ if (config.tty) printf("(integer) ");
return cliReadSingleLineReply(fd,0);
case '$':
return cliReadBulkReply(fd);
case '*':
return cliReadMultiBulkReply(fd);
default:
- printf("protocol error, got '%c' as reply type byte\n", type);
+ printf("protocol error, got '%c' as reply type byte", type);
return 1;
}
}
return 0;
}
+static void showInteractiveHelp(void) {
+ printf(
+ "\n"
+ "Welcome to redis-cli " REDIS_VERSION "!\n"
+ "Just type any valid Redis command to see a pretty printed output.\n"
+ "\n"
+ "It is possible to quote strings, like in:\n"
+ " set \"my key\" \"some string \\xff\\n\"\n"
+ "\n"
+ "You can find a list of valid Redis commands at\n"
+ " http://code.google.com/p/redis/wiki/CommandReference\n"
+ "\n"
+ "Note: redis-cli supports line editing, use up/down arrows for history."
+ "\n\n");
+}
+
static int cliSendCommand(int argc, char **argv, int repeat) {
char *command = argv[0];
int fd, j, retval = 0;
sds cmd;
config.raw_output = !strcasecmp(command,"info");
+ if (!strcasecmp(command,"help")) {
+ showInteractiveHelp();
+ return 0;
+ }
if (!strcasecmp(command,"shutdown")) config.shutdown = 1;
if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
if (!strcasecmp(command,"subscribe") ||
!strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
- if ((fd = cliConnect()) == -1) return 1;
+ if ((fd = cliConnect(0)) == -1) return 1;
/* Select db number */
retval = selectDb(fd);
while(repeat--) {
anetWrite(fd,cmd,sdslen(cmd));
while (config.monitor_mode) {
- cliReadSingleLineReply(fd,0);
+ if (cliReadSingleLineReply(fd,0)) exit(1);
+ printf("\n");
}
if (config.pubsub_mode) {
printf("Reading messages... (press Ctrl-c to quit)\n");
while (1) {
cliReadReply(fd);
- printf("\n");
+ printf("\n\n");
}
}
retval = cliReadReply(fd);
- if (retval) {
- return retval;
- }
+ if (!config.raw_output && config.tty) printf("\n");
+ if (retval) return retval;
}
return 0;
}
i++;
} else if (!strcmp(argv[i],"-h") && lastarg) {
usage();
+ } else if (!strcmp(argv[i],"-x")) {
+ config.stdinarg = 1;
} else if (!strcmp(argv[i],"-p") && !lastarg) {
config.hostport = atoi(argv[i+1]);
i++;
config.auth = argv[i+1];
i++;
} else if (!strcmp(argv[i],"-i")) {
- config.interactive = 1;
+ fprintf(stderr,
+"Starting interactive mode using -i is deprecated. Interactive mode is started\n"
+"by default when redis-cli is executed without a command to execute.\n"
+ );
} else if (!strcmp(argv[i],"-c")) {
- config.argn_from_stdin = 1;
+ fprintf(stderr,
+"Reading last argument from standard input using -c is deprecated.\n"
+"When standard input is connected to a pipe or regular file, it is\n"
+"automatically used as last argument.\n"
+ );
} else if (!strcmp(argv[i],"-v")) {
printf("redis-cli shipped with Redis verison %s\n", REDIS_VERSION);
exit(0);
static void usage() {
fprintf(stderr, "usage: redis-cli [-iv] [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 arg3 ... argN\n");
- fprintf(stderr, "usage: echo \"argN\" | redis-cli -c [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 ... arg(N-1)\n");
- fprintf(stderr, "\nIf a pipe from standard input is detected this data is used as last argument.\n\n");
- fprintf(stderr, "example: cat /etc/passwd | redis-cli set my_passwd\n");
+ fprintf(stderr, "usage: echo \"argN\" | redis-cli -x [options] cmd arg1 arg2 ... arg(N-1)\n\n");
+ fprintf(stderr, "example: cat /etc/passwd | redis-cli -x set my_passwd\n");
fprintf(stderr, "example: redis-cli get my_passwd\n");
fprintf(stderr, "example: redis-cli -r 100 lpush mylist x\n");
fprintf(stderr, "\nRun in interactive mode: redis-cli -i or just don't pass any command\n");
char *line;
sds *argv;
+ config.interactive = 1;
while((line = linenoise("redis> ")) != NULL) {
if (line[0] != '\0') {
argv = sdssplitargs(line,&argc);
linenoiseHistoryAdd(line);
if (config.historyfile) linenoiseHistorySave(config.historyfile);
- if (argc > 0) {
+ if (argv == NULL) {
+ printf("Invalid argument(s)\n");
+ continue;
+ } else if (argc > 0) {
if (strcasecmp(argv[0],"quit") == 0 ||
strcasecmp(argv[0],"exit") == 0)
- exit(0);
- else
- cliSendCommand(argc, argv, 1);
+ {
+ exit(0);
+ } else {
+ int err;
+
+ if ((err = cliSendCommand(argc, argv, 1)) != 0) {
+ if (err == ECONNRESET) {
+ printf("Reconnecting... ");
+ fflush(stdout);
+ if (cliConnect(1) == -1) exit(1);
+ printf("OK\n");
+ cliSendCommand(argc,argv,1);
+ }
+ }
+ }
}
/* Free the argument vector */
for (j = 0; j < argc; j++)
exit(0);
}
+static int noninteractive(int argc, char **argv) {
+ int retval = 0;
+ if (config.stdinarg) {
+ argv = zrealloc(argv, (argc+1)*sizeof(char*));
+ argv[argc] = readArgFromStdin();
+ retval = cliSendCommand(argc+1, argv, config.repeat);
+ } else {
+ /* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */
+ retval = cliSendCommand(argc, argv, config.repeat);
+ }
+ return retval;
+}
+
int main(int argc, char **argv) {
int firstarg;
- char **argvcopy;
config.hostip = "127.0.0.1";
config.hostport = 6379;
config.repeat = 1;
config.dbnum = 0;
- config.argn_from_stdin = 0;
- config.shutdown = 0;
config.interactive = 0;
+ config.shutdown = 0;
config.monitor_mode = 0;
config.pubsub_mode = 0;
config.raw_output = 0;
+ config.stdinarg = 0;
config.auth = NULL;
config.historyfile = NULL;
+ config.tty = isatty(fileno(stdout)) || (getenv("FAKETTY") != NULL);
+ config.mb_sep = '\n';
if (getenv("HOME") != NULL) {
config.historyfile = malloc(256);
if (config.auth != NULL) {
char *authargv[2];
+ int dbnum = config.dbnum;
+ /* We need to save the real configured database number and set it to
+ * zero here, otherwise cliSendCommand() will try to perform the
+ * SELECT command before the authentication, and it will fail. */
+ config.dbnum = 0;
authargv[0] = "AUTH";
authargv[1] = config.auth;
cliSendCommand(2, convertToSds(2, authargv), 1);
+ config.dbnum = dbnum; /* restore the right DB number */
}
- if (argc == 0 || config.interactive == 1) repl();
-
- argvcopy = convertToSds(argc+1, argv);
- if (config.argn_from_stdin) {
- sds lastarg = readArgFromStdin();
- argvcopy[argc] = lastarg;
- argc++;
- }
- return cliSendCommand(argc, argvcopy, config.repeat);
+ /* Start interactive mode when no command is provided */
+ if (argc == 0) repl();
+ /* Otherwise, we have some arguments to execute */
+ return noninteractive(argc,convertToSds(argc,argv));
}
#include <float.h>
#include <math.h>
#include <pthread.h>
+#include <sys/resource.h>
/* Our shared "common" objects */
server.hash_max_zipmap_value = REDIS_HASH_MAX_ZIPMAP_VALUE;
server.list_max_ziplist_entries = REDIS_LIST_MAX_ZIPLIST_ENTRIES;
server.list_max_ziplist_value = REDIS_LIST_MAX_ZIPLIST_VALUE;
+ server.set_max_intset_entries = REDIS_SET_MAX_INTSET_ENTRIES;
server.shutdown_asap = 0;
resetServerSaveParams();
} else if (c->multibulk) {
if (c->bulklen == -1) {
if (((char*)c->argv[0]->ptr)[0] != '$') {
- addReplySds(c,sdsnew("-ERR multi bulk protocol error\r\n"));
+ addReplyError(c,"multi bulk protocol error");
resetClient(c);
return 1;
} else {
- int bulklen = atoi(((char*)c->argv[0]->ptr)+1);
+ char *eptr;
+ long bulklen = strtol(((char*)c->argv[0]->ptr)+1,&eptr,10);
+ int perr = eptr[0] != '\0';
+
decrRefCount(c->argv[0]);
- if (bulklen < 0 || bulklen > 1024*1024*1024) {
+ if (perr || bulklen == LONG_MIN || bulklen == LONG_MAX ||
+ bulklen < 0 || bulklen > 1024*1024*1024)
+ {
c->argc--;
- addReplySds(c,sdsnew("-ERR invalid bulk write count\r\n"));
+ addReplyError(c,"invalid bulk write count");
resetClient(c);
return 1;
}
* such wrong arity, bad command name and so forth. */
cmd = lookupCommand(c->argv[0]->ptr);
if (!cmd) {
- addReplySds(c,
- sdscatprintf(sdsempty(), "-ERR unknown command '%s'\r\n",
- (char*)c->argv[0]->ptr));
+ addReplyErrorFormat(c,"unknown command '%s'",
+ (char*)c->argv[0]->ptr);
resetClient(c);
return 1;
} else if ((cmd->arity > 0 && cmd->arity != c->argc) ||
(c->argc < -cmd->arity)) {
- addReplySds(c,
- sdscatprintf(sdsempty(),
- "-ERR wrong number of arguments for '%s' command\r\n",
- cmd->name));
+ addReplyErrorFormat(c,"wrong number of arguments for '%s' command",
+ cmd->name);
resetClient(c);
return 1;
} else if (cmd->flags & REDIS_CMD_BULK && c->bulklen == -1) {
/* This is a bulk command, we have to read the last argument yet. */
- int bulklen = atoi(c->argv[c->argc-1]->ptr);
+ char *eptr;
+ long bulklen = strtol(c->argv[c->argc-1]->ptr,&eptr,10);
+ int perr = eptr[0] != '\0';
decrRefCount(c->argv[c->argc-1]);
- if (bulklen < 0 || bulklen > 1024*1024*1024) {
+ if (perr || bulklen == LONG_MAX || bulklen == LONG_MIN ||
+ bulklen < 0 || bulklen > 1024*1024*1024)
+ {
c->argc--;
- addReplySds(c,sdsnew("-ERR invalid bulk write count\r\n"));
+ addReplyError(c,"invalid bulk write count");
resetClient(c);
return 1;
}
/* Check if the user is authenticated */
if (server.requirepass && !c->authenticated && cmd->proc != authCommand) {
- addReplySds(c,sdsnew("-ERR operation not permitted\r\n"));
+ addReplyError(c,"operation not permitted");
resetClient(c);
return 1;
}
if (server.maxmemory && (cmd->flags & REDIS_CMD_DENYOOM) &&
zmalloc_used_memory() > server.maxmemory)
{
- addReplySds(c,sdsnew("-ERR command not allowed when used memory > 'maxmemory'\r\n"));
+ addReplyError(c,"command not allowed when used memory > 'maxmemory'");
resetClient(c);
return 1;
}
&&
cmd->proc != subscribeCommand && cmd->proc != unsubscribeCommand &&
cmd->proc != psubscribeCommand && cmd->proc != punsubscribeCommand) {
- addReplySds(c,sdsnew("-ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context\r\n"));
+ addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context");
resetClient(c);
return 1;
}
if (server.vm_enabled) unlink(server.vm_swap_file);
} else {
/* Snapshotting. Perform a SYNC SAVE and exit */
- if (rdbSave(server.dbfilename) == REDIS_OK) {
- if (server.daemonize)
- unlink(server.pidfile);
- redisLog(REDIS_WARNING,"%zu bytes used at exit",zmalloc_used_memory());
- } else {
+ if (rdbSave(server.dbfilename) != REDIS_OK) {
/* Ooops.. error saving! The best we can do is to continue
* operating. Note that if there was a background saving process,
* in the next cron() Redis will be notified that the background
return REDIS_ERR;
}
}
+ if (server.daemonize) unlink(server.pidfile);
redisLog(REDIS_WARNING,"Server exit now, bye bye...");
return REDIS_OK;
}
addReply(c,shared.ok);
} else {
c->authenticated = 0;
- addReplySds(c,sdscatprintf(sdsempty(),"-ERR invalid password\r\n"));
+ addReplyError(c,"invalid password");
}
}
time_t uptime = time(NULL)-server.stat_starttime;
int j;
char hmem[64];
+ struct rusage self_ru, c_ru;
+
+ getrusage(RUSAGE_SELF, &self_ru);
+ getrusage(RUSAGE_CHILDREN, &c_ru);
bytesToHuman(hmem,zmalloc_used_memory());
info = sdscatprintf(sdsempty(),
"process_id:%ld\r\n"
"uptime_in_seconds:%ld\r\n"
"uptime_in_days:%ld\r\n"
+ "used_cpu_sys:%.2f\r\n"
+ "used_cpu_user:%.2f\r\n"
+ "used_cpu_sys_childrens:%.2f\r\n"
+ "used_cpu_user_childrens:%.2f\r\n"
"connected_clients:%d\r\n"
"connected_slaves:%d\r\n"
"blocked_clients:%d\r\n"
"used_memory:%zu\r\n"
"used_memory_human:%s\r\n"
+ "mem_fragmentation_ratio:%.2f\r\n"
"changes_since_last_save:%lld\r\n"
"bgsave_in_progress:%d\r\n"
"last_save_time:%ld\r\n"
(long) getpid(),
uptime,
uptime/(3600*24),
+ (float)self_ru.ru_utime.tv_sec+(float)self_ru.ru_utime.tv_usec/1000000,
+ (float)self_ru.ru_stime.tv_sec+(float)self_ru.ru_stime.tv_usec/1000000,
+ (float)c_ru.ru_utime.tv_sec+(float)c_ru.ru_utime.tv_usec/1000000,
+ (float)c_ru.ru_stime.tv_sec+(float)c_ru.ru_stime.tv_usec/1000000,
listLength(server.clients)-listLength(server.slaves),
listLength(server.slaves),
server.blpop_blocked_clients,
zmalloc_used_memory(),
hmem,
+ zmalloc_get_fragmentation_ratio(),
server.dirty,
server.bgsavechildpid != -1,
server.lastsave,
if (tryFreeOneObjectFromFreelist() == REDIS_OK) continue;
for (j = 0; j < server.dbnum; j++) {
int minttl = -1;
- robj *minkey = NULL;
+ sds minkey = NULL;
+ robj *keyobj = NULL;
struct dictEntry *de;
if (dictSize(server.db[j].expires)) {
minttl = t;
}
}
- dbDelete(server.db+j,minkey);
+ keyobj = createStringObject(minkey,sdslen(minkey));
+ dbDelete(server.db+j,keyobj);
+ server.stat_expiredkeys++;
+ decrRefCount(keyobj);
}
}
if (!freed) return; /* nothing to free... */
}
#endif /* __linux__ */
+void createPidFile(void) {
+ /* Try to write the pid file in a best-effort way. */
+ FILE *fp = fopen(server.pidfile,"w");
+ if (fp) {
+ fprintf(fp,"%d\n",getpid());
+ fclose(fp);
+ }
+}
+
void daemonize(void) {
int fd;
- FILE *fp;
if (fork() != 0) exit(0); /* parent exits */
setsid(); /* create a new session */
dup2(fd, STDERR_FILENO);
if (fd > STDERR_FILENO) close(fd);
}
- /* Try to write the pid file */
- fp = fopen(server.pidfile,"w");
- if (fp) {
- fprintf(fp,"%d\n",getpid());
- fclose(fp);
- }
}
void version() {
}
if (server.daemonize) daemonize();
initServer();
+ if (server.daemonize) createPidFile();
redisLog(REDIS_NOTICE,"Server started, Redis version " REDIS_VERSION);
#ifdef __linux__
linuxOvercommitMemoryWarning();
redisLog(REDIS_WARNING,"%s", messages[i]);
/* free(messages); Don't call free() with possibly corrupted memory. */
+ if (server.daemonize) unlink(server.pidfile);
_exit(0);
}
#include "anet.h" /* Networking the easy way */
#include "zipmap.h" /* Compact string -> string data structure */
#include "ziplist.h" /* Compact list data structure */
+#include "intset.h" /* Compact integer set structure */
#include "version.h"
/* Error codes */
#define REDIS_MAX_WRITE_PER_EVENT (1024*64)
#define REDIS_REQUEST_MAX_SIZE (1024*1024*256) /* max bytes in inline command */
#define REDIS_SHARED_INTEGERS 10000
+#define REDIS_REPLY_CHUNK_BYTES (5*1500) /* 5 TCP packets with default MTU */
/* If more then REDIS_WRITEV_THRESHOLD write packets are pending use writev */
#define REDIS_WRITEV_THRESHOLD 3
#define REDIS_ENCODING_ZIPMAP 3 /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
+#define REDIS_ENCODING_INTSET 6 /* Encoded as intset */
/* Object types only used for dumping to disk */
#define REDIS_EXPIRETIME 253
#define REDIS_HASH_MAX_ZIPMAP_VALUE 512
#define REDIS_LIST_MAX_ZIPLIST_ENTRIES 1024
#define REDIS_LIST_MAX_ZIPLIST_VALUE 32
+#define REDIS_SET_MAX_INTSET_ENTRIES 4096
/* Sets operations codes */
#define REDIS_OP_UNION 0
int dictid;
sds querybuf;
robj **argv, **mbargv;
+ char *newline; /* pointing to the detected newline in querybuf */
int argc, mbargc;
- int bulklen; /* bulk read len. -1 if not in bulk read mode */
+ long bulklen; /* bulk read len. -1 if not in bulk read mode */
int multibulk; /* multi bulk command format active */
list *reply;
int sentlen;
list *watched_keys; /* Keys WATCHED for MULTI/EXEC CAS */
dict *pubsub_channels; /* channels a client is interested in (SUBSCRIBE) */
list *pubsub_patterns; /* patterns a client is interested in (SUBSCRIBE) */
+
+ /* Response buffer */
+ int bufpos;
+ char buf[REDIS_REPLY_CHUNK_BYTES];
} redisClient;
struct saveparam {
int fd;
redisDb *db;
long long dirty; /* changes to DB from the last save */
+ long long dirty_before_bgsave; /* used to restore dirty on failed BGSAVE */
list *clients;
list *slaves, *monitors;
char neterr[ANET_ERR_LEN];
size_t hash_max_zipmap_value;
size_t list_max_ziplist_entries;
size_t list_max_ziplist_value;
+ size_t set_max_intset_entries;
/* Virtual memory state */
FILE *vm_fp;
int vm_fd;
listNode *ln; /* Entry in linked list */
} listTypeEntry;
+/* Structure to hold set iteration abstraction. */
+typedef struct {
+ robj *subject;
+ int encoding;
+ int ii; /* intset iterator */
+ dictIterator *di;
+} setTypeIterator;
+
/* Structure to hold hash iteration abstration. Note that iteration over
* hashes involves both fields and values. Because it is possible that
* not both are required, store pointers in the iterator to avoid
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask);
void sendReplyToClientWritev(aeEventLoop *el, int fd, void *privdata, int mask);
void addReply(redisClient *c, robj *obj);
+void *addDeferredMultiBulkLength(redisClient *c);
+void setDeferredMultiBulkLength(redisClient *c, void *node, long length);
void addReplySds(redisClient *c, sds s);
void processInputBuffer(redisClient *c);
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void addReply(redisClient *c, robj *obj);
void addReplySds(redisClient *c, sds s);
+void addReplyError(redisClient *c, char *err);
+void addReplyStatus(redisClient *c, char *status);
void addReplyDouble(redisClient *c, double d);
void addReplyLongLong(redisClient *c, long long ll);
-void addReplyUlong(redisClient *c, unsigned long ul);
+void addReplyMultiBulkLen(redisClient *c, long length);
void *dupClientReplyValue(void *o);
+#ifdef __GNUC__
+void addReplyErrorFormat(redisClient *c, const char *fmt, ...)
+ __attribute__((format(printf, 2, 3)));
+void addReplyStatusFormat(redisClient *c, const char *fmt, ...)
+ __attribute__((format(printf, 2, 3)));
+#else
+void addReplyErrorFormat(redisClient *c, const char *fmt, ...);
+void addReplyStatusFormat(redisClient *c, const char *fmt, ...);
+#endif
+
/* List data type */
void listTypeTryConversion(robj *subject, robj *value);
void listTypePush(robj *subject, robj *value, int where);
robj *createListObject(void);
robj *createZiplistObject(void);
robj *createSetObject(void);
+robj *createIntsetObject(void);
robj *createHashObject(void);
robj *createZsetObject(void);
int getLongFromObjectOrReply(redisClient *c, robj *o, long *target, const char *msg);
void handleClientsBlockedOnSwappedKey(redisDb *db, robj *key);
vmpointer *vmSwapObjectBlocking(robj *val);
+/* Set data type */
+robj *setTypeCreate(robj *value);
+int setTypeAdd(robj *subject, robj *value);
+int setTypeRemove(robj *subject, robj *value);
+int setTypeIsMember(robj *subject, robj *value);
+setTypeIterator *setTypeInitIterator(robj *subject);
+void setTypeReleaseIterator(setTypeIterator *si);
+robj *setTypeNext(setTypeIterator *si);
+robj *setTypeRandomElement(robj *subject);
+unsigned long setTypeSize(robj *subject);
+void setTypeConvert(robj *subject, int enc);
+
/* Hash data type */
void convertToRealHash(robj *o);
void hashTypeTryConversion(robj *subject, robj **argv, int start, int end);
long long memtoll(const char *p, int *err);
int ll2string(char *s, size_t len, long long value);
int isStringRepresentableAsLong(sds s, long *longval);
+int isStringRepresentableAsLongLong(sds s, long long *longval);
+int isObjectRepresentableAsLongLong(robj *o, long long *llongval);
/* Configuration */
void loadServerConfig(char *filename);
while(size) {
if (aeWait(fd,AE_READABLE,1000) & AE_READABLE) {
nread = read(fd,ptr,size);
- if (nread == -1) return -1;
+ if (nread <= 0) return -1;
ptr += nread;
size -= nread;
totread += nread;
/* ignore SYNC if aleady slave or in monitor mode */
if (c->flags & REDIS_SLAVE) return;
+ /* Refuse SYNC requests if we are a slave but the link with our master
+ * is not ok... */
+ if (server.masterhost && server.replstate != REDIS_REPL_CONNECTED) {
+ addReplyError(c,"Can't SYNC while not connected with my master");
+ return;
+ }
+
/* SYNC can't be issued when the server has pending data to send to
* the client about already issued commands. We need a fresh reply
* buffer registering the differences between the BGSAVE and the current
* dataset, so that we can copy to other slaves if needed. */
if (listLength(c->reply) != 0) {
- addReplySds(c,sdsnew("-ERR SYNC is invalid with pending input\r\n"));
+ addReplyError(c,"SYNC is invalid with pending input");
return;
}
redisLog(REDIS_NOTICE,"Starting BGSAVE for SYNC");
if (rdbSaveBackground(server.dbfilename) != REDIS_OK) {
redisLog(REDIS_NOTICE,"Replication failed, can't BGSAVE");
- addReplySds(c,sdsnew("-ERR Unalbe to perform background save\r\n"));
+ addReplyError(c,"Unable to perform background save");
return;
}
c->replstate = REDIS_REPL_WAIT_BGSAVE_END;
strerror(errno));
return REDIS_ERR;
}
- if (buf[0] != '$') {
+ if (buf[0] == '-') {
+ close(fd);
+ redisLog(REDIS_WARNING,"MASTER aborted replication with an error: %s",
+ buf+1);
+ return REDIS_ERR;
+ } else if (buf[0] != '$') {
close(fd);
redisLog(REDIS_WARNING,"Bad protocol from MASTER, the first byte is not '$', are you sure the host and port are right?");
return REDIS_ERR;
int nread, nwritten;
nread = read(fd,buf,(dumpsize < 1024)?dumpsize:1024);
- if (nread == -1) {
+ if (nread <= 0) {
redisLog(REDIS_WARNING,"I/O error trying to sync with MASTER: %s",
- strerror(errno));
+ (nread == -1) ? strerror(errno) : "connection lost");
close(fd);
close(dfd);
return REDIS_ERR;
#include "sds.h"
#include <stdio.h>
#include <stdlib.h>
-#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include "zmalloc.h"
return sdscpylen(s, t, strlen(t));
}
-sds sdscatprintf(sds s, const char *fmt, ...) {
- va_list ap;
+sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
+ va_list cpy;
char *buf, *t;
size_t buflen = 16;
if (buf == NULL) return NULL;
#endif
buf[buflen-2] = '\0';
- va_start(ap, fmt);
- vsnprintf(buf, buflen, fmt, ap);
- va_end(ap);
+ va_copy(cpy,ap);
+ vsnprintf(buf, buflen, fmt, cpy);
if (buf[buflen-2] != '\0') {
zfree(buf);
buflen *= 2;
return t;
}
+sds sdscatprintf(sds s, const char *fmt, ...) {
+ va_list ap;
+ char *t;
+ va_start(ap, fmt);
+ t = sdscatvprintf(s,fmt,ap);
+ va_end(ap);
+ return t;
+}
+
sds sdstrim(sds s, const char *cset) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
char *start, *end, *sp, *ep;
if (*p) {
/* get a token */
int inq=0; /* set to 1 if we are in "quotes" */
- int done = 0;
+ int done=0;
if (current == NULL) current = sdsempty();
while(!done) {
}
current = sdscatlen(current,&c,1);
} else if (*p == '"') {
- done = 1;
+ /* closing quote must be followed by a space */
+ if (*(p+1) && !isspace(*(p+1))) goto err;
+ done=1;
+ } else if (!*p) {
+ /* unterminated quotes */
+ goto err;
} else {
current = sdscatlen(current,p,1);
}
return vector;
}
}
+
+err:
+ while((*argc)--)
+ sdsfree(vector[*argc]);
+ zfree(vector);
+ if (current) sdsfree(current);
+ return NULL;
}
#define __SDS_H
#include <sys/types.h>
+#include <stdarg.h>
typedef char *sds;
sds sdscpylen(sds s, char *t, size_t len);
sds sdscpy(sds s, char *t);
+sds sdscatvprintf(sds s, const char *fmt, va_list ap);
#ifdef __GNUC__
sds sdscatprintf(sds s, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
/* Solaris specific fixes */
#if defined(__GNUC__)
+#include <math.h>
#undef isnan
#define isnan(x) \
__extension__({ __typeof (x) __x_a = (x); \
/* Load the sorting vector with all the objects to sort */
switch(sortval->type) {
case REDIS_LIST: vectorlen = listTypeLength(sortval); break;
- case REDIS_SET: vectorlen = dictSize((dict*)sortval->ptr); break;
+ case REDIS_SET: vectorlen = setTypeSize(sortval); break;
case REDIS_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break;
default: vectorlen = 0; redisPanic("Bad SORT type"); /* Avoid GCC warning */
}
j++;
}
listTypeReleaseIterator(li);
- } else {
- dict *set;
+ } else if (sortval->type == REDIS_SET) {
+ setTypeIterator *si = setTypeInitIterator(sortval);
+ robj *ele;
+ while((ele = setTypeNext(si)) != NULL) {
+ vector[j].obj = ele;
+ vector[j].u.score = 0;
+ vector[j].u.cmpobj = NULL;
+ j++;
+ }
+ setTypeReleaseIterator(si);
+ } else if (sortval->type == REDIS_ZSET) {
+ dict *set = ((zset*)sortval->ptr)->dict;
dictIterator *di;
dictEntry *setele;
-
- if (sortval->type == REDIS_SET) {
- set = sortval->ptr;
- } else {
- zset *zs = sortval->ptr;
- set = zs->dict;
- }
-
di = dictGetIterator(set);
while((setele = dictNext(di)) != NULL) {
vector[j].obj = dictGetEntryKey(setele);
j++;
}
dictReleaseIterator(di);
+ } else {
+ redisPanic("Unknown type");
}
redisAssert(j == vectorlen);
outputlen = getop ? getop*(end-start+1) : end-start+1;
if (storekey == NULL) {
/* STORE option not specified, sent the sorting result to client */
- addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",outputlen));
+ addReplyMultiBulkLen(c,outputlen);
for (j = start; j <= end; j++) {
listNode *ln;
listIter li;
* replaced. */
server.dirty += 1+outputlen;
touchWatchedKey(c->db,storekey);
- addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",outputlen));
+ addReplyLongLong(c,outputlen);
}
/* Cleanup */
- if (sortval->type == REDIS_LIST)
+ if (sortval->type == REDIS_LIST || sortval->type == REDIS_SET)
for (j = 0; j < vectorlen; j++)
decrRefCount(vector[j].obj);
decrRefCount(sortval);
robj *o;
if ((c->argc % 2) == 1) {
- addReplySds(c,sdsnew("-ERR wrong number of arguments for HMSET\r\n"));
+ addReplyError(c,"wrong number of arguments for HMSET");
return;
}
/* Note the check for o != NULL happens inside the loop. This is
* done because objects that cannot be found are considered to be
* an empty hash. The reply should then be a series of NULLs. */
- addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->argc-2));
+ addReplyMultiBulkLen(c,c->argc-2);
for (i = 2; i < c->argc; i++) {
if (o != NULL && (value = hashTypeGet(o,c->argv[i])) != NULL) {
addReplyBulk(c,value);
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_HASH)) return;
- addReplyUlong(c,hashTypeLength(o));
+ addReplyLongLong(c,hashTypeLength(o));
}
void genericHgetallCommand(redisClient *c, int flags) {
- robj *o, *lenobj, *obj;
+ robj *o, *obj;
unsigned long count = 0;
hashTypeIterator *hi;
+ void *replylen = NULL;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
|| checkType(c,o,REDIS_HASH)) return;
- lenobj = createObject(REDIS_STRING,NULL);
- addReply(c,lenobj);
- decrRefCount(lenobj);
-
+ replylen = addDeferredMultiBulkLength(c);
hi = hashTypeInitIterator(o);
while (hashTypeNext(hi) != REDIS_ERR) {
if (flags & REDIS_HASH_KEY) {
}
}
hashTypeReleaseIterator(hi);
-
- lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",count);
+ setDeferredMultiBulkLength(c,replylen,count);
}
void hkeysCommand(redisClient *c) {
server.dirty++;
}
- addReplyUlong(c,listTypeLength(subject));
+ addReplyLongLong(c,listTypeLength(subject));
}
void lpushxCommand(redisClient *c) {
void llenCommand(redisClient *c) {
robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.czero);
if (o == NULL || checkType(c,o,REDIS_LIST)) return;
- addReplyUlong(c,listTypeLength(o));
+ addReplyLongLong(c,listTypeLength(o));
}
void lindexCommand(redisClient *c) {
rangelen = (end-start)+1;
/* Return the result in form of a multi-bulk reply */
- addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",rangelen));
+ addReplyMultiBulkLen(c,rangelen);
listTypeIterator *li = listTypeInitIterator(o,start,REDIS_TAIL);
for (j = 0; j < rangelen; j++) {
redisAssert(listTypeNext(li,&entry));
decrRefCount(obj);
if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]);
- addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",removed));
+ addReplyLongLong(c,removed);
if (removed) touchWatchedKey(c->db,c->argv[1]);
}
redisAssert(ln != NULL);
receiver = ln->value;
- addReplySds(receiver,sdsnew("*2\r\n"));
+ addReplyMultiBulkLen(receiver,2);
addReplyBulk(receiver,key);
addReplyBulk(receiver,ele);
unblockClientWaitingData(receiver);
/* Blocking RPOP/LPOP */
void blockingPopGenericCommand(redisClient *c, int where) {
robj *o;
+ long long lltimeout;
time_t timeout;
int j;
+ /* Make sure timeout is an integer value */
+ if (getLongLongFromObjectOrReply(c,c->argv[c->argc-1],&lltimeout,
+ "timeout is not an integer") != REDIS_OK) return;
+
+ /* Make sure the timeout is not negative */
+ if (lltimeout < 0) {
+ addReplyError(c,"timeout is negative");
+ return;
+ }
+
for (j = 1; j < c->argc-1; j++) {
o = lookupKeyWrite(c->db,c->argv[j]);
if (o != NULL) {
* "real" command will add the last element (the value)
* for us. If this souds like an hack to you it's just
* because it is... */
- addReplySds(c,sdsnew("*2\r\n"));
+ addReplyMultiBulkLen(c,2);
addReplyBulk(c,argv[1]);
popGenericCommand(c,where);
}
}
}
+
+ /* If we are inside a MULTI/EXEC and the list is empty the only thing
+ * we can do is treating it as a timeout (even with timeout 0). */
+ if (c->flags & REDIS_MULTI) {
+ addReply(c,shared.nullmultibulk);
+ return;
+ }
+
/* If the list is empty or the key does not exists we must block */
- timeout = strtol(c->argv[c->argc-1]->ptr,NULL,10);
+ timeout = lltimeout;
if (timeout > 0) timeout += time(NULL);
blockForKeys(c,c->argv+1,c->argc-2,timeout);
}
* Set Commands
*----------------------------------------------------------------------------*/
+/* Factory method to return a set that *can* hold "value". When the object has
+ * an integer-encodable value, an intset will be returned. Otherwise a regular
+ * hash table. */
+robj *setTypeCreate(robj *value) {
+ if (isObjectRepresentableAsLongLong(value,NULL) == REDIS_OK)
+ return createIntsetObject();
+ return createSetObject();
+}
+
+int setTypeAdd(robj *subject, robj *value) {
+ long long llval;
+ if (subject->encoding == REDIS_ENCODING_HT) {
+ if (dictAdd(subject->ptr,value,NULL) == DICT_OK) {
+ incrRefCount(value);
+ return 1;
+ }
+ } else if (subject->encoding == REDIS_ENCODING_INTSET) {
+ if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
+ uint8_t success = 0;
+ subject->ptr = intsetAdd(subject->ptr,llval,&success);
+ if (success) {
+ /* Convert to regular set when the intset contains
+ * too many entries. */
+ if (intsetLen(subject->ptr) > server.set_max_intset_entries)
+ setTypeConvert(subject,REDIS_ENCODING_HT);
+ return 1;
+ }
+ } else {
+ /* Failed to get integer from object, convert to regular set. */
+ setTypeConvert(subject,REDIS_ENCODING_HT);
+
+ /* The set *was* an intset and this value is not integer
+ * encodable, so dictAdd should always work. */
+ redisAssert(dictAdd(subject->ptr,value,NULL) == DICT_OK);
+ incrRefCount(value);
+ return 1;
+ }
+ } else {
+ redisPanic("Unknown set encoding");
+ }
+ return 0;
+}
+
+int setTypeRemove(robj *subject, robj *value) {
+ long long llval;
+ if (subject->encoding == REDIS_ENCODING_HT) {
+ if (dictDelete(subject->ptr,value) == DICT_OK) {
+ if (htNeedsResize(subject->ptr)) dictResize(subject->ptr);
+ return 1;
+ }
+ } else if (subject->encoding == REDIS_ENCODING_INTSET) {
+ if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
+ uint8_t success;
+ subject->ptr = intsetRemove(subject->ptr,llval,&success);
+ if (success) return 1;
+ }
+ } else {
+ redisPanic("Unknown set encoding");
+ }
+ return 0;
+}
+
+int setTypeIsMember(robj *subject, robj *value) {
+ long long llval;
+ if (subject->encoding == REDIS_ENCODING_HT) {
+ return dictFind((dict*)subject->ptr,value) != NULL;
+ } else if (subject->encoding == REDIS_ENCODING_INTSET) {
+ if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
+ return intsetFind((intset*)subject->ptr,llval);
+ }
+ } else {
+ redisPanic("Unknown set encoding");
+ }
+ return 0;
+}
+
+setTypeIterator *setTypeInitIterator(robj *subject) {
+ setTypeIterator *si = zmalloc(sizeof(setTypeIterator));
+ si->subject = subject;
+ si->encoding = subject->encoding;
+ if (si->encoding == REDIS_ENCODING_HT) {
+ si->di = dictGetIterator(subject->ptr);
+ } else if (si->encoding == REDIS_ENCODING_INTSET) {
+ si->ii = 0;
+ } else {
+ redisPanic("Unknown set encoding");
+ }
+ return si;
+}
+
+void setTypeReleaseIterator(setTypeIterator *si) {
+ if (si->encoding == REDIS_ENCODING_HT)
+ dictReleaseIterator(si->di);
+ zfree(si);
+}
+
+/* Move to the next entry in the set. Returns the object at the current
+ * position, or NULL when the end is reached. This object will have its
+ * refcount incremented, so the caller needs to take care of this. */
+robj *setTypeNext(setTypeIterator *si) {
+ robj *ret = NULL;
+ if (si->encoding == REDIS_ENCODING_HT) {
+ dictEntry *de = dictNext(si->di);
+ if (de != NULL) {
+ ret = dictGetEntryKey(de);
+ incrRefCount(ret);
+ }
+ } else if (si->encoding == REDIS_ENCODING_INTSET) {
+ int64_t llval;
+ if (intsetGet(si->subject->ptr,si->ii++,&llval))
+ ret = createStringObjectFromLongLong(llval);
+ }
+ return ret;
+}
+
+
+/* Return random element from set. The returned object will always have
+ * an incremented refcount. */
+robj *setTypeRandomElement(robj *subject) {
+ robj *ret = NULL;
+ if (subject->encoding == REDIS_ENCODING_HT) {
+ dictEntry *de = dictGetRandomKey(subject->ptr);
+ ret = dictGetEntryKey(de);
+ incrRefCount(ret);
+ } else if (subject->encoding == REDIS_ENCODING_INTSET) {
+ long long llval = intsetRandom(subject->ptr);
+ ret = createStringObjectFromLongLong(llval);
+ } else {
+ redisPanic("Unknown set encoding");
+ }
+ return ret;
+}
+
+unsigned long setTypeSize(robj *subject) {
+ if (subject->encoding == REDIS_ENCODING_HT) {
+ return dictSize((dict*)subject->ptr);
+ } else if (subject->encoding == REDIS_ENCODING_INTSET) {
+ return intsetLen((intset*)subject->ptr);
+ } else {
+ redisPanic("Unknown set encoding");
+ }
+}
+
+/* Convert the set to specified encoding. The resulting dict (when converting
+ * to a hashtable) is presized to hold the number of elements in the original
+ * set. */
+void setTypeConvert(robj *subject, int enc) {
+ setTypeIterator *si;
+ robj *element;
+ redisAssert(subject->type == REDIS_SET);
+
+ if (enc == REDIS_ENCODING_HT) {
+ dict *d = dictCreate(&setDictType,NULL);
+ /* Presize the dict to avoid rehashing */
+ dictExpand(d,intsetLen(subject->ptr));
+
+ /* setTypeGet returns a robj with incremented refcount */
+ si = setTypeInitIterator(subject);
+ while ((element = setTypeNext(si)) != NULL)
+ redisAssert(dictAdd(d,element,NULL) == DICT_OK);
+ setTypeReleaseIterator(si);
+
+ subject->encoding = REDIS_ENCODING_HT;
+ zfree(subject->ptr);
+ subject->ptr = d;
+ } else {
+ redisPanic("Unsupported set conversion");
+ }
+}
+
void saddCommand(redisClient *c) {
robj *set;
set = lookupKeyWrite(c->db,c->argv[1]);
if (set == NULL) {
- set = createSetObject();
+ set = setTypeCreate(c->argv[2]);
dbAdd(c->db,c->argv[1],set);
} else {
if (set->type != REDIS_SET) {
return;
}
}
- if (dictAdd(set->ptr,c->argv[2],NULL) == DICT_OK) {
- incrRefCount(c->argv[2]);
+ if (setTypeAdd(set,c->argv[2])) {
touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
addReply(c,shared.cone);
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,set,REDIS_SET)) return;
- if (dictDelete(set->ptr,c->argv[2]) == DICT_OK) {
- server.dirty++;
+ if (setTypeRemove(set,c->argv[2])) {
+ if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
touchWatchedKey(c->db,c->argv[1]);
- if (htNeedsResize(set->ptr)) dictResize(set->ptr);
- if (dictSize((dict*)set->ptr) == 0) dbDelete(c->db,c->argv[1]);
+ server.dirty++;
addReply(c,shared.cone);
} else {
addReply(c,shared.czero);
}
void smoveCommand(redisClient *c) {
- robj *srcset, *dstset;
-
+ robj *srcset, *dstset, *ele;
srcset = lookupKeyWrite(c->db,c->argv[1]);
dstset = lookupKeyWrite(c->db,c->argv[2]);
+ ele = c->argv[3];
- /* If the source key does not exist return 0, if it's of the wrong type
- * raise an error */
- if (srcset == NULL || srcset->type != REDIS_SET) {
- addReply(c, srcset ? shared.wrongtypeerr : shared.czero);
+ /* If the source key does not exist return 0 */
+ if (srcset == NULL) {
+ addReply(c,shared.czero);
return;
}
- /* Error if the destination key is not a set as well */
- if (dstset && dstset->type != REDIS_SET) {
- addReply(c,shared.wrongtypeerr);
+
+ /* If the source key has the wrong type, or the destination key
+ * is set and has the wrong type, return with an error. */
+ if (checkType(c,srcset,REDIS_SET) ||
+ (dstset && checkType(c,dstset,REDIS_SET))) return;
+
+ /* If srcset and dstset are equal, SMOVE is a no-op */
+ if (srcset == dstset) {
+ addReply(c,shared.cone);
return;
}
- /* Remove the element from the source set */
- if (dictDelete(srcset->ptr,c->argv[3]) == DICT_ERR) {
- /* Key not found in the src set! return zero */
+
+ /* If the element cannot be removed from the src set, return 0. */
+ if (!setTypeRemove(srcset,ele)) {
addReply(c,shared.czero);
return;
}
- if (dictSize((dict*)srcset->ptr) == 0 && srcset != dstset)
- dbDelete(c->db,c->argv[1]);
+
+ /* Remove the src set from the database when empty */
+ if (setTypeSize(srcset) == 0) dbDelete(c->db,c->argv[1]);
touchWatchedKey(c->db,c->argv[1]);
touchWatchedKey(c->db,c->argv[2]);
server.dirty++;
- /* Add the element to the destination set */
+
+ /* Create the destination set when it doesn't exist */
if (!dstset) {
- dstset = createSetObject();
+ dstset = setTypeCreate(ele);
dbAdd(c->db,c->argv[2],dstset);
}
- if (dictAdd(dstset->ptr,c->argv[3],NULL) == DICT_OK)
- incrRefCount(c->argv[3]);
+
+ /* An extra key has changed when ele was successfully added to dstset */
+ if (setTypeAdd(dstset,ele)) server.dirty++;
addReply(c,shared.cone);
}
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,set,REDIS_SET)) return;
- if (dictFind(set->ptr,c->argv[2]))
+ if (setTypeIsMember(set,c->argv[2]))
addReply(c,shared.cone);
else
addReply(c,shared.czero);
void scardCommand(redisClient *c) {
robj *o;
- dict *s;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_SET)) return;
- s = o->ptr;
- addReplyUlong(c,dictSize(s));
+ addReplyLongLong(c,setTypeSize(o));
}
void spopCommand(redisClient *c) {
- robj *set;
- dictEntry *de;
+ robj *set, *ele;
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,set,REDIS_SET)) return;
- de = dictGetRandomKey(set->ptr);
- if (de == NULL) {
+ ele = setTypeRandomElement(set);
+ if (ele == NULL) {
addReply(c,shared.nullbulk);
} else {
- robj *ele = dictGetEntryKey(de);
-
+ setTypeRemove(set,ele);
addReplyBulk(c,ele);
- dictDelete(set->ptr,ele);
- if (htNeedsResize(set->ptr)) dictResize(set->ptr);
- if (dictSize((dict*)set->ptr) == 0) dbDelete(c->db,c->argv[1]);
+ decrRefCount(ele);
+ if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
}
}
void srandmemberCommand(redisClient *c) {
- robj *set;
- dictEntry *de;
+ robj *set, *ele;
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,set,REDIS_SET)) return;
- de = dictGetRandomKey(set->ptr);
- if (de == NULL) {
+ ele = setTypeRandomElement(set);
+ if (ele == NULL) {
addReply(c,shared.nullbulk);
} else {
- robj *ele = dictGetEntryKey(de);
-
addReplyBulk(c,ele);
+ decrRefCount(ele);
}
}
int qsortCompareSetsByCardinality(const void *s1, const void *s2) {
- dict **d1 = (void*) s1, **d2 = (void*) s2;
-
- return dictSize(*d1)-dictSize(*d2);
+ return setTypeSize(*(robj**)s1)-setTypeSize(*(robj**)s2);
}
-void sinterGenericCommand(redisClient *c, robj **setskeys, unsigned long setsnum, robj *dstkey) {
- dict **dv = zmalloc(sizeof(dict*)*setsnum);
- dictIterator *di;
- dictEntry *de;
- robj *lenobj = NULL, *dstset = NULL;
+void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum, robj *dstkey) {
+ robj **sets = zmalloc(sizeof(robj*)*setnum);
+ setTypeIterator *si;
+ robj *ele, *dstset = NULL;
+ void *replylen = NULL;
unsigned long j, cardinality = 0;
- for (j = 0; j < setsnum; j++) {
- robj *setobj;
-
- setobj = dstkey ?
- lookupKeyWrite(c->db,setskeys[j]) :
- lookupKeyRead(c->db,setskeys[j]);
+ for (j = 0; j < setnum; j++) {
+ robj *setobj = dstkey ?
+ lookupKeyWrite(c->db,setkeys[j]) :
+ lookupKeyRead(c->db,setkeys[j]);
if (!setobj) {
- zfree(dv);
+ zfree(sets);
if (dstkey) {
if (dbDelete(c->db,dstkey)) {
touchWatchedKey(c->db,dstkey);
}
return;
}
- if (setobj->type != REDIS_SET) {
- zfree(dv);
- addReply(c,shared.wrongtypeerr);
+ if (checkType(c,setobj,REDIS_SET)) {
+ zfree(sets);
return;
}
- dv[j] = setobj->ptr;
+ sets[j] = setobj;
}
/* Sort sets from the smallest to largest, this will improve our
* algorithm's performace */
- qsort(dv,setsnum,sizeof(dict*),qsortCompareSetsByCardinality);
+ qsort(sets,setnum,sizeof(robj*),qsortCompareSetsByCardinality);
/* The first thing we should output is the total number of elements...
* since this is a multi-bulk write, but at this stage we don't know
* to the output list and save the pointer to later modify it with the
* right length */
if (!dstkey) {
- lenobj = createObject(REDIS_STRING,NULL);
- addReply(c,lenobj);
- decrRefCount(lenobj);
+ replylen = addDeferredMultiBulkLength(c);
} else {
/* If we have a target key where to store the resulting set
* create this key with an empty set inside */
- dstset = createSetObject();
+ dstset = createIntsetObject();
}
/* Iterate all the elements of the first (smallest) set, and test
* the element against all the other sets, if at least one set does
* not include the element it is discarded */
- di = dictGetIterator(dv[0]);
-
- while((de = dictNext(di)) != NULL) {
- robj *ele;
-
- for (j = 1; j < setsnum; j++)
- if (dictFind(dv[j],dictGetEntryKey(de)) == NULL) break;
- if (j != setsnum)
- continue; /* at least one set does not contain the member */
- ele = dictGetEntryKey(de);
- if (!dstkey) {
- addReplyBulk(c,ele);
- cardinality++;
- } else {
- dictAdd(dstset->ptr,ele,NULL);
- incrRefCount(ele);
+ si = setTypeInitIterator(sets[0]);
+ while((ele = setTypeNext(si)) != NULL) {
+ for (j = 1; j < setnum; j++)
+ if (!setTypeIsMember(sets[j],ele)) break;
+
+ /* Only take action when all sets contain the member */
+ if (j == setnum) {
+ if (!dstkey) {
+ addReplyBulk(c,ele);
+ cardinality++;
+ } else {
+ setTypeAdd(dstset,ele);
+ }
}
+ decrRefCount(ele);
}
- dictReleaseIterator(di);
+ setTypeReleaseIterator(si);
if (dstkey) {
/* Store the resulting set into the target, if the intersection
* is not an empty set. */
dbDelete(c->db,dstkey);
- if (dictSize((dict*)dstset->ptr) > 0) {
+ if (setTypeSize(dstset) > 0) {
dbAdd(c->db,dstkey,dstset);
- addReplyLongLong(c,dictSize((dict*)dstset->ptr));
+ addReplyLongLong(c,setTypeSize(dstset));
} else {
decrRefCount(dstset);
addReply(c,shared.czero);
touchWatchedKey(c->db,dstkey);
server.dirty++;
} else {
- lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",cardinality);
+ setDeferredMultiBulkLength(c,replylen,cardinality);
}
- zfree(dv);
+ zfree(sets);
}
void sinterCommand(redisClient *c) {
sinterGenericCommand(c,c->argv+2,c->argc-2,c->argv[1]);
}
-void sunionDiffGenericCommand(redisClient *c, robj **setskeys, int setsnum, robj *dstkey, int op) {
- dict **dv = zmalloc(sizeof(dict*)*setsnum);
- dictIterator *di;
- dictEntry *de;
- robj *dstset = NULL;
- int j, cardinality = 0;
+#define REDIS_OP_UNION 0
+#define REDIS_OP_DIFF 1
+#define REDIS_OP_INTER 2
- for (j = 0; j < setsnum; j++) {
- robj *setobj;
+void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *dstkey, int op) {
+ robj **sets = zmalloc(sizeof(robj*)*setnum);
+ setTypeIterator *si;
+ robj *ele, *dstset = NULL;
+ int j, cardinality = 0;
- setobj = dstkey ?
- lookupKeyWrite(c->db,setskeys[j]) :
- lookupKeyRead(c->db,setskeys[j]);
+ for (j = 0; j < setnum; j++) {
+ robj *setobj = dstkey ?
+ lookupKeyWrite(c->db,setkeys[j]) :
+ lookupKeyRead(c->db,setkeys[j]);
if (!setobj) {
- dv[j] = NULL;
+ sets[j] = NULL;
continue;
}
- if (setobj->type != REDIS_SET) {
- zfree(dv);
- addReply(c,shared.wrongtypeerr);
+ if (checkType(c,setobj,REDIS_SET)) {
+ zfree(sets);
return;
}
- dv[j] = setobj->ptr;
+ sets[j] = setobj;
}
/* We need a temp set object to store our union. If the dstkey
* is not NULL (that is, we are inside an SUNIONSTORE operation) then
* this set object will be the resulting object to set into the target key*/
- dstset = createSetObject();
+ dstset = createIntsetObject();
/* Iterate all the elements of all the sets, add every element a single
* time to the result set */
- for (j = 0; j < setsnum; j++) {
- if (op == REDIS_OP_DIFF && j == 0 && !dv[j]) break; /* result set is empty */
- if (!dv[j]) continue; /* non existing keys are like empty sets */
-
- di = dictGetIterator(dv[j]);
+ for (j = 0; j < setnum; j++) {
+ if (op == REDIS_OP_DIFF && j == 0 && !sets[j]) break; /* result set is empty */
+ if (!sets[j]) continue; /* non existing keys are like empty sets */
- while((de = dictNext(di)) != NULL) {
- robj *ele;
-
- /* dictAdd will not add the same element multiple times */
- ele = dictGetEntryKey(de);
+ si = setTypeInitIterator(sets[j]);
+ while((ele = setTypeNext(si)) != NULL) {
if (op == REDIS_OP_UNION || j == 0) {
- if (dictAdd(dstset->ptr,ele,NULL) == DICT_OK) {
- incrRefCount(ele);
+ if (setTypeAdd(dstset,ele)) {
cardinality++;
}
} else if (op == REDIS_OP_DIFF) {
- if (dictDelete(dstset->ptr,ele) == DICT_OK) {
+ if (setTypeRemove(dstset,ele)) {
cardinality--;
}
}
+ decrRefCount(ele);
}
- dictReleaseIterator(di);
+ setTypeReleaseIterator(si);
- /* result set is empty? Exit asap. */
+ /* Exit when result set is empty. */
if (op == REDIS_OP_DIFF && cardinality == 0) break;
}
/* Output the content of the resulting set, if not in STORE mode */
if (!dstkey) {
- addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",cardinality));
- di = dictGetIterator(dstset->ptr);
- while((de = dictNext(di)) != NULL) {
- robj *ele;
-
- ele = dictGetEntryKey(de);
+ addReplyMultiBulkLen(c,cardinality);
+ si = setTypeInitIterator(dstset);
+ while((ele = setTypeNext(si)) != NULL) {
addReplyBulk(c,ele);
+ decrRefCount(ele);
}
- dictReleaseIterator(di);
+ setTypeReleaseIterator(si);
decrRefCount(dstset);
} else {
/* If we have a target key where to store the resulting set
* create this key with the result set inside */
dbDelete(c->db,dstkey);
- if (dictSize((dict*)dstset->ptr) > 0) {
+ if (setTypeSize(dstset) > 0) {
dbAdd(c->db,dstkey,dstset);
- addReplyLongLong(c,dictSize((dict*)dstset->ptr));
+ addReplyLongLong(c,setTypeSize(dstset));
} else {
decrRefCount(dstset);
addReply(c,shared.czero);
touchWatchedKey(c->db,dstkey);
server.dirty++;
}
- zfree(dv);
+ zfree(sets);
}
void sunionCommand(redisClient *c) {
if (getLongFromObjectOrReply(c, expire, &seconds, NULL) != REDIS_OK)
return;
if (seconds <= 0) {
- addReplySds(c,sdsnew("-ERR invalid expire time in SETEX\r\n"));
+ addReplyError(c,"invalid expire time in SETEX");
return;
}
}
void mgetCommand(redisClient *c) {
int j;
- addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",c->argc-1));
+ addReplyMultiBulkLen(c,c->argc-1);
for (j = 1; j < c->argc; j++) {
robj *o = lookupKeyRead(c->db,c->argv[j]);
if (o == NULL) {
int j, busykeys = 0;
if ((c->argc % 2) == 0) {
- addReplySds(c,sdsnew("-ERR wrong number of arguments for MSET\r\n"));
+ addReplyError(c,"wrong number of arguments for MSET");
return;
}
/* Handle the NX flag. The MSETNX semantic is to return zero and don't
}
touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
- addReplySds(c,sdscatprintf(sdsempty(),":%lu\r\n",(unsigned long)totlen));
+ addReplyLongLong(c,totlen);
}
void substrCommand(redisClient *c) {
*score = scoreval;
}
if (isnan(*score)) {
- addReplySds(c,
- sdsnew("-ERR resulting score is not a number (NaN)\r\n"));
+ addReplyError(c,"resulting score is not a number (NaN)");
zfree(score);
/* Note that we don't need to check if the zset may be empty and
* should be removed here, as we can only obtain Nan as score if
/* expect setnum input keys to be given */
setnum = atoi(c->argv[2]->ptr);
if (setnum < 1) {
- addReplySds(c,sdsnew("-ERR at least 1 input key is needed for ZUNIONSTORE/ZINTERSTORE\r\n"));
+ addReplyError(c,
+ "at least 1 input key is needed for ZUNIONSTORE/ZINTERSTORE");
return;
}
}
/* Return the result in form of a multi-bulk reply */
- addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",
- withscores ? (rangelen*2) : rangelen));
+ addReplyMultiBulkLen(c,withscores ? (rangelen*2) : rangelen);
for (j = 0; j < rangelen; j++) {
ele = ln->obj;
addReplyBulk(c,ele);
if (c->argc != (4 + withscores) && c->argc != (7 + withscores))
badsyntax = 1;
if (badsyntax) {
- addReplySds(c,
- sdsnew("-ERR wrong number of arguments for ZRANGEBYSCORE\r\n"));
+ addReplyError(c,"wrong number of arguments for ZRANGEBYSCORE");
return;
}
zset *zsetobj = o->ptr;
zskiplist *zsl = zsetobj->zsl;
zskiplistNode *ln;
- robj *ele, *lenobj = NULL;
+ robj *ele;
+ void *replylen = NULL;
unsigned long rangelen = 0;
/* Get the first node with the score >= min, or with
* are in the list, so we push this object that will represent
* the multi-bulk length in the output buffer, and will "fix"
* it later */
- if (!justcount) {
- lenobj = createObject(REDIS_STRING,NULL);
- addReply(c,lenobj);
- decrRefCount(lenobj);
- }
+ if (!justcount)
+ replylen = addDeferredMultiBulkLength(c);
while(ln && (maxex ? (ln->score < max) : (ln->score <= max))) {
if (offset) {
if (justcount) {
addReplyLongLong(c,(long)rangelen);
} else {
- lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",
+ setDeferredMultiBulkLength(c,replylen,
withscores ? (rangelen*2) : rangelen);
}
}
checkType(c,o,REDIS_ZSET)) return;
zs = o->ptr;
- addReplyUlong(c,zs->zsl->length);
+ addReplyLongLong(c,zs->zsl->length);
}
void zscoreCommand(redisClient *c) {
return l;
}
-/* Check if the nul-terminated string 's' can be represented by a long
+/* Check if the sds string 's' can be represented by a long long
* (that is, is a number that fits into long without any other space or
- * character before or after the digits).
+ * character before or after the digits, so that converting this number
+ * back to a string will result in the same bytes as the original string).
*
- * If so, the function returns REDIS_OK and *longval is set to the value
+ * If so, the function returns REDIS_OK and *llongval is set to the value
* of the number. Otherwise REDIS_ERR is returned */
-int isStringRepresentableAsLong(sds s, long *longval) {
+int isStringRepresentableAsLongLong(sds s, long long *llongval) {
char buf[32], *endptr;
- long value;
+ long long value;
int slen;
- value = strtol(s, &endptr, 10);
+ value = strtoll(s, &endptr, 10);
if (endptr[0] != '\0') return REDIS_ERR;
slen = ll2string(buf,32,value);
/* If the number converted back into a string is not identical
* then it's not possible to encode the string as integer */
if (sdslen(s) != (unsigned)slen || memcmp(buf,s,slen)) return REDIS_ERR;
- if (longval) *longval = value;
+ if (llongval) *llongval = value;
+ return REDIS_OK;
+}
+
+int isStringRepresentableAsLong(sds s, long *longval) {
+ long long ll;
+
+ if (isStringRepresentableAsLongLong(s,&ll) == REDIS_ERR) return REDIS_ERR;
+ if (ll < LONG_MIN || ll > LONG_MAX) return REDIS_ERR;
+ *longval = (long)ll;
return REDIS_OK;
}
+
+int isObjectRepresentableAsLongLong(robj *o, long long *llongval) {
+ redisAssert(o->type == REDIS_STRING);
+ if (o->encoding == REDIS_ENCODING_INT) {
+ if (llongval) *llongval = (long) o->ptr;
+ return REDIS_OK;
+ } else {
+ return isStringRepresentableAsLongLong(o->ptr,llongval);
+ }
+}
-#define REDIS_VERSION "2.1.2"
+#define REDIS_VERSION "2.1.4"
/* LZF requires a lot of stack */
pthread_attr_init(&server.io_threads_attr);
pthread_attr_getstacksize(&server.io_threads_attr, &stacksize);
+
+ /* Solaris may report a stacksize of 0, let's set it to 1 otherwise
+ * multiplying it by 2 in the while loop later will not really help ;) */
+ if (!stacksize) stacksize = 1;
+
while (stacksize < REDIS_THREAD_STACK_SIZE) stacksize *= 2;
pthread_attr_setstacksize(&server.io_threads_attr, stacksize);
/* Listen for events in the threaded I/O pipe */
z = (o->type == REDIS_ZSET);
d = z ? ((zset*)o->ptr)->dict : o->ptr;
- asize = sizeof(dict)+(sizeof(struct dictEntry*)*dictSlots(d));
- if (z) asize += sizeof(zset)-sizeof(dict);
- if (dictSize(d)) {
- de = dictGetRandomKey(d);
- ele = dictGetEntryKey(de);
- elesize = (ele->encoding == REDIS_ENCODING_RAW) ?
- (sizeof(*o)+sdslen(ele->ptr)) : sizeof(*o);
- asize += (sizeof(struct dictEntry)+elesize)*dictSize(d);
- if (z) asize += sizeof(zskiplistNode)*dictSize(d);
+ if (!z && o->encoding == REDIS_ENCODING_INTSET) {
+ intset *is = o->ptr;
+ asize = sizeof(*is)+is->encoding*is->length;
+ } else {
+ asize = sizeof(dict)+(sizeof(struct dictEntry*)*dictSlots(d));
+ if (z) asize += sizeof(zset)-sizeof(dict);
+ if (dictSize(d)) {
+ de = dictGetRandomKey(d);
+ ele = dictGetEntryKey(de);
+ elesize = (ele->encoding == REDIS_ENCODING_RAW) ?
+ (sizeof(*o)+sdslen(ele->ptr)) : sizeof(*o);
+ asize += (sizeof(struct dictEntry)+elesize)*dictSize(d);
+ if (z) asize += sizeof(zskiplistNode)*dictSize(d);
+ }
}
break;
case REDIS_HASH:
/* Every time a thread finished a Job, it writes a byte into the write side
* of an unix pipe in order to "awake" the main thread, and this function
- * is called. */
+ * is called.
+ *
+ * Note that this is called both by the event loop, when a I/O thread
+ * sends a byte in the notification pipe, and is also directly called from
+ * waitEmptyIOJobsQueue().
+ *
+ * In the latter case we don't want to swap more, so we use the
+ * "privdata" argument setting it to a not NULL value to signal this
+ * condition. */
void vmThreadedIOCompletedJob(aeEventLoop *el, int fd, void *privdata,
int mask)
{
REDIS_NOTUSED(mask);
REDIS_NOTUSED(privdata);
+ if (privdata != NULL) trytoswap = 0; /* check the comments above... */
+
/* For every byte we read in the read side of the pipe, there is one
* I/O job completed to process. */
while((retval = read(fd,buf,1)) == 1) {
io_processed_len = listLength(server.io_processed);
unlockThreadedIO();
if (io_processed_len) {
- vmThreadedIOCompletedJob(NULL,server.io_ready_pipe_read,NULL,0);
+ vmThreadedIOCompletedJob(NULL,server.io_ready_pipe_read,
+ (void*)0xdeadbeef,0);
usleep(1000); /* 1 millisecond */
} else {
usleep(10000); /* 10 milliseconds */
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
+
#include "config.h"
#if defined(__sun)
void zmalloc_enable_thread_safeness(void) {
zmalloc_thread_safe = 1;
}
+
+/* Fragmentation = RSS / allocated-bytes */
+
+#if defined(HAVE_PROCFS)
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+float zmalloc_get_fragmentation_ratio(void) {
+ size_t allocated = zmalloc_used_memory();
+ int page = sysconf(_SC_PAGESIZE);
+ size_t rss;
+ char buf[4096];
+ char filename[256];
+ int fd, count;
+ char *p, *x;
+
+ snprintf(filename,256,"/proc/%d/stat",getpid());
+ if ((fd = open(filename,O_RDONLY)) == -1) return 0;
+ if (read(fd,buf,4096) <= 0) {
+ close(fd);
+ return 0;
+ }
+ close(fd);
+
+ p = buf;
+ count = 23; /* RSS is the 24th field in /proc/<pid>/stat */
+ while(p && count--) {
+ p = strchr(p,' ');
+ if (p) p++;
+ }
+ if (!p) return 0;
+ x = strchr(p,' ');
+ if (!x) return 0;
+ *x = '\0';
+
+ rss = strtoll(p,NULL,10);
+ rss *= page;
+ return (float)rss/allocated;
+}
+#elif defined(HAVE_TASKINFO)
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <mach/task.h>
+#include <mach/mach_init.h>
+
+float zmalloc_get_fragmentation_ratio(void) {
+ task_t task = MACH_PORT_NULL;
+ struct task_basic_info t_info;
+ mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;
+
+ if (task_for_pid(current_task(), getpid(), &task) != KERN_SUCCESS)
+ return 0;
+ task_info(task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count);
+
+ return (float)t_info.resident_size/zmalloc_used_memory();
+}
+#else
+float zmalloc_get_fragmentation_ratio(void) {
+ return 0;
+}
+#endif
char *zstrdup(const char *s);
size_t zmalloc_used_memory(void);
void zmalloc_enable_thread_safeness(void);
+float zmalloc_get_fragmentation_ratio(void);
#endif /* _ZMALLOC_H */
--- /dev/null
+start_server {tags {"cli"}} {
+ proc open_cli {} {
+ set ::env(TERM) dumb
+ set fd [open [format "|src/redis-cli -p %d -n 9" [srv port]] "r+"]
+ fconfigure $fd -buffering none
+ fconfigure $fd -blocking false
+ fconfigure $fd -translation binary
+ assert_equal "redis> " [read_cli $fd]
+ set _ $fd
+ }
+
+ proc close_cli {fd} {
+ close $fd
+ }
+
+ proc read_cli {fd} {
+ set buf [read $fd]
+ while {[string length $buf] == 0} {
+ # wait some time and try again
+ after 10
+ set buf [read $fd]
+ }
+ set _ $buf
+ }
+
+ proc write_cli {fd buf} {
+ puts $fd $buf
+ flush $fd
+ }
+
+ # Helpers to run tests in interactive mode
+ proc run_command {fd cmd} {
+ write_cli $fd $cmd
+ set lines [split [read_cli $fd] "\n"]
+ assert_equal "redis> " [lindex $lines end]
+ join [lrange $lines 0 end-1] "\n"
+ }
+
+ proc test_interactive_cli {name code} {
+ set ::env(FAKETTY) 1
+ set fd [open_cli]
+ test "Interactive CLI: $name" $code
+ close_cli $fd
+ unset ::env(FAKETTY)
+ }
+
+ # Helpers to run tests where stdout is not a tty
+ proc write_tmpfile {contents} {
+ set tmp [tmpfile "cli"]
+ set tmpfd [open $tmp "w"]
+ puts -nonewline $tmpfd $contents
+ close $tmpfd
+ set _ $tmp
+ }
+
+ proc _run_cli {opts args} {
+ set cmd [format "src/redis-cli -p %d -n 9 $args" [srv port]]
+ foreach {key value} $opts {
+ if {$key eq "pipe"} {
+ set cmd "sh -c \"$value | $cmd\""
+ }
+ if {$key eq "path"} {
+ set cmd "$cmd < $value"
+ }
+ }
+
+ set fd [open "|$cmd" "r"]
+ fconfigure $fd -buffering none
+ fconfigure $fd -translation binary
+ set resp [read $fd 1048576]
+ close $fd
+ set _ $resp
+ }
+
+ proc run_cli {args} {
+ _run_cli {} {*}$args
+ }
+
+ proc run_cli_with_input_pipe {cmd args} {
+ _run_cli [list pipe $cmd] {*}$args
+ }
+
+ proc run_cli_with_input_file {path args} {
+ _run_cli [list path $path] {*}$args
+ }
+
+ proc test_nontty_cli {name code} {
+ test "Non-interactive non-TTY CLI: $name" $code
+ }
+
+ # Helpers to run tests where stdout is a tty (fake it)
+ proc test_tty_cli {name code} {
+ set ::env(FAKETTY) 1
+ test "Non-interactive TTY CLI: $name" $code
+ unset ::env(FAKETTY)
+ }
+
+ test_interactive_cli "INFO response should be printed raw" {
+ set lines [split [run_command $fd info] "\n"]
+ foreach line $lines {
+ assert [regexp {^[a-z0-9_]+:[a-z0-9_]+} $line]
+ }
+ }
+
+ test_interactive_cli "Status reply" {
+ assert_equal "OK" [run_command $fd "set key foo"]
+ }
+
+ test_interactive_cli "Integer reply" {
+ assert_equal "(integer) 1" [run_command $fd "incr counter"]
+ }
+
+ test_interactive_cli "Bulk reply" {
+ r set key foo
+ assert_equal "\"foo\"" [run_command $fd "get key"]
+ }
+
+ test_interactive_cli "Multi-bulk reply" {
+ r rpush list foo
+ r rpush list bar
+ assert_equal "1. \"foo\"\n2. \"bar\"" [run_command $fd "lrange list 0 -1"]
+ }
+
+ test_interactive_cli "Parsing quotes" {
+ assert_equal "OK" [run_command $fd "set key \"bar\""]
+ assert_equal "bar" [r get key]
+ assert_equal "OK" [run_command $fd "set key \" bar \""]
+ assert_equal " bar " [r get key]
+ assert_equal "OK" [run_command $fd "set key \"\\\"bar\\\"\""]
+ assert_equal "\"bar\"" [r get key]
+ assert_equal "OK" [run_command $fd "set key \"\tbar\t\""]
+ assert_equal "\tbar\t" [r get key]
+
+ # invalid quotation
+ assert_equal "Invalid argument(s)" [run_command $fd "get \"\"key"]
+ assert_equal "Invalid argument(s)" [run_command $fd "get \"key\"x"]
+
+ # quotes after the argument are weird, but should be allowed
+ assert_equal "OK" [run_command $fd "set key\"\" bar"]
+ assert_equal "bar" [r get key]
+ }
+
+ test_tty_cli "Status reply" {
+ assert_equal "OK\n" [run_cli set key bar]
+ assert_equal "bar" [r get key]
+ }
+
+ test_tty_cli "Integer reply" {
+ r del counter
+ assert_equal "(integer) 1\n" [run_cli incr counter]
+ }
+
+ test_tty_cli "Bulk reply" {
+ r set key "tab\tnewline\n"
+ assert_equal "\"tab\\tnewline\\n\"\n" [run_cli get key]
+ }
+
+ test_tty_cli "Multi-bulk reply" {
+ r del list
+ r rpush list foo
+ r rpush list bar
+ assert_equal "1. \"foo\"\n2. \"bar\"\n" [run_cli lrange list 0 -1]
+ }
+
+ test_tty_cli "Read last argument from pipe" {
+ assert_equal "OK\n" [run_cli_with_input_pipe "echo foo" set key]
+ assert_equal "foo\n" [r get key]
+ }
+
+ test_tty_cli "Read last argument from file" {
+ set tmpfile [write_tmpfile "from file"]
+ assert_equal "OK\n" [run_cli_with_input_file $tmpfile set key]
+ assert_equal "from file" [r get key]
+ }
+
+ test_nontty_cli "Status reply" {
+ assert_equal "OK" [run_cli set key bar]
+ assert_equal "bar" [r get key]
+ }
+
+ test_nontty_cli "Integer reply" {
+ r del counter
+ assert_equal "1" [run_cli incr counter]
+ }
+
+ test_nontty_cli "Bulk reply" {
+ r set key "tab\tnewline\n"
+ assert_equal "tab\tnewline\n" [run_cli get key]
+ }
+
+ test_nontty_cli "Multi-bulk reply" {
+ r del list
+ r rpush list foo
+ r rpush list bar
+ assert_equal "foo\nbar" [run_cli lrange list 0 -1]
+ }
+
+ test_nontty_cli "Read last argument from pipe" {
+ assert_equal "OK" [run_cli_with_input_pipe "echo foo" set key]
+ assert_equal "foo\n" [r get key]
+ }
+
+ test_nontty_cli "Read last argument from file" {
+ set tmpfile [write_tmpfile "from file"]
+ assert_equal "OK" [run_cli_with_input_file $tmpfile set key]
+ assert_equal "from file" [r get key]
+ }
+}
}
close $fd
} e]} {
- puts "Can't PING server at $host:$port... $e"
+ puts -nonewline "."
+ } else {
+ puts -nonewline "ok"
}
return $retval
}
if {$::valgrind} {
exec valgrind src/redis-server $config_file > $stdout 2> $stderr &
- after 2000
} else {
exec src/redis-server $config_file > $stdout 2> $stderr &
- after 500
}
# check that the server actually started
- if {$code ne "undefined" && ![ping_server $::host $::port]} {
+ # ugly but tries to be as fast as possible...
+ set retrynum 20
+ set serverisup 0
+
+ puts -nonewline "=== ($tags) Starting server ${::host}:${::port} "
+ after 10
+ if {$code ne "undefined"} {
+ while {[incr retrynum -1]} {
+ catch {
+ if {[ping_server $::host $::port]} {
+ set serverisup 1
+ }
+ }
+ if {$serverisup} break
+ after 50
+ }
+ } else {
+ set serverisup 1
+ }
+ puts {}
+
+ if {!$serverisup} {
error_and_quit $config_file [exec cat $stderr]
}
# execute provided block
set curnum $::testnum
- catch { uplevel 1 $code } err
+ if {![catch { uplevel 1 $code } err]} {
+ # zero exit status is good
+ unset err
+ }
+
if {$curnum == $::testnum} {
# don't check for leaks when no tests were executed
dict set srv "skipleaks" 1
# allow an exception to bubble up the call chain but still kill this
# server, because we want to reuse the ports when the tests are re-run
- if {$err eq "exception"} {
- puts [format "Logged warnings (pid %d):" [dict get $srv "pid"]]
- set warnings [warnings_from_file [dict get $srv "stdout"]]
- if {[string length $warnings] > 0} {
- puts "$warnings"
- } else {
- puts "(none)"
+ if {[info exists err]} {
+ if {$err eq "exception"} {
+ puts [format "Logged warnings (pid %d):" [dict get $srv "pid"]]
+ set warnings [warnings_from_file [dict get $srv "stdout"]]
+ if {[string length $warnings] > 0} {
+ puts "$warnings"
+ } else {
+ puts "(none)"
+ }
+ # kill this server without checking for leaks
+ dict set srv "skipleaks" 1
+ kill_server $srv
+ error "exception"
+ } elseif {[string length $err] > 0} {
+ puts "Error executing the suite, aborting..."
+ puts $err
+ exit 1
}
- # kill this server without checking for leaks
- dict set srv "skipleaks" 1
- kill_server $srv
- error "exception"
- } elseif {[string length $err] > 0} {
- puts "Error executing the suite, aborting..."
- puts $err
- exit 1
}
set ::tags [lrange $::tags 0 end-[llength $tags]]
# are nested, use "srv 0 pid" to get the pid of the inner server. To access
# outer servers, use "srv -1 pid" etcetera.
set ::servers {}
-proc srv {level property} {
+proc srv {args} {
+ set level 0
+ if {[string is integer [lindex $args 0]]} {
+ set level [lindex $args 0]
+ set property [lindex $args 1]
+ } else {
+ set property [lindex $args 0]
+ }
set srv [lindex $::servers end+$level]
dict get $srv $property
}
execute_tests "unit/cas"
execute_tests "integration/replication"
execute_tests "integration/aof"
+# execute_tests "integration/redis-cli"
execute_tests "unit/pubsub"
# run tests with VM enabled
-start_server {} {
+start_server {tags {"other"}} {
test {SAVE - make sure there are all the types as values} {
# Wait for a background saving in progress to terminate
waitForBgsave r
-start_server {} {
+start_server {tags {"protocol"}} {
test {Handle an empty query well} {
set fd [r channel]
puts -nonewline $fd "\r\n"
gets $fd
} {*invalid bulk*count*}
+ test {bulk payload is not a number} {
+ set fd [r channel]
+ puts -nonewline $fd "SET x blabla\r\n"
+ flush $fd
+ gets $fd
+ } {*invalid bulk*count*}
+
test {Multi bulk request not followed by bulk args} {
set fd [r channel]
puts -nonewline $fd "*1\r\nfoo\r\n"
-start_server {tags {"sort"}} {
- test {SORT ALPHA against integer encoded strings} {
+start_server {
+ tags {"sort"}
+ overrides {
+ "list-max-ziplist-value" 16
+ "list-max-ziplist-entries" 32
+ "set-max-intset-entries" 32
+ }
+} {
+ proc create_random_dataset {num cmd} {
+ set tosort {}
+ set result {}
+ array set seenrand {}
+ r del tosort
+ for {set i 0} {$i < $num} {incr i} {
+ # Make sure all the weights are different because
+ # Redis does not use a stable sort but Tcl does.
+ while 1 {
+ randpath {
+ set rint [expr int(rand()*1000000)]
+ } {
+ set rint [expr rand()]
+ }
+ if {![info exists seenrand($rint)]} break
+ }
+ set seenrand($rint) x
+ r $cmd tosort $i
+ r set weight_$i $rint
+ r hset wobj_$i weight $rint
+ lappend tosort [list $i $rint]
+ }
+ set sorted [lsort -index 1 -real $tosort]
+ for {set i 0} {$i < $num} {incr i} {
+ lappend result [lindex $sorted $i 0]
+ }
+ set _ $result
+ }
+
+ foreach {num cmd enc title} {
+ 16 lpush ziplist "Ziplist"
+ 1000 lpush linkedlist "Linked list"
+ 10000 lpush linkedlist "Big Linked list"
+ 16 sadd intset "Intset"
+ 1000 sadd hashtable "Hash table"
+ 10000 sadd hashtable "Big Hash table"
+ } {
+ set result [create_random_dataset $num $cmd]
+ assert_encoding $enc tosort
+
+ test "$title: SORT BY key" {
+ assert_equal $result [r sort tosort {BY weight_*}]
+ }
+
+ test "$title: SORT BY hash field" {
+ assert_equal $result [r sort tosort {BY wobj_*->weight}]
+ }
+ }
+
+ set result [create_random_dataset 16 lpush]
+ test "SORT GET #" {
+ assert_equal [lsort -integer $result] [r sort tosort GET #]
+ }
+
+ test "SORT GET <const>" {
+ r del foo
+ set res [r sort tosort GET foo]
+ assert_equal 16 [llength $res]
+ foreach item $res { assert_equal {} $item }
+ }
+
+ test "SORT GET (key and hash) with sanity check" {
+ set l1 [r sort tosort GET # GET weight_*]
+ set l2 [r sort tosort GET # GET wobj_*->weight]
+ foreach {id1 w1} $l1 {id2 w2} $l2 {
+ assert_equal $id1 $id2
+ assert_equal $w1 [r get weight_$id1]
+ assert_equal $w2 [r get weight_$id1]
+ }
+ }
+
+ test "SORT BY key STORE" {
+ r sort tosort {BY weight_*} store sort-res
+ assert_equal $result [r lrange sort-res 0 -1]
+ assert_equal 16 [r llen sort-res]
+ assert_encoding ziplist sort-res
+ }
+
+ test "SORT BY hash field STORE" {
+ r sort tosort {BY wobj_*->weight} store sort-res
+ assert_equal $result [r lrange sort-res 0 -1]
+ assert_equal 16 [r llen sort-res]
+ assert_encoding ziplist sort-res
+ }
+
+ test "SORT DESC" {
+ assert_equal [lsort -decreasing -integer $result] [r sort tosort {DESC}]
+ }
+
+ test "SORT ALPHA against integer encoded strings" {
r del mylist
r lpush mylist 2
r lpush mylist 1
r sort mylist alpha
} {1 10 2 3}
- tags {"slow"} {
- set res {}
- test {Create a random list and a random set} {
- set tosort {}
- array set seenrand {}
- for {set i 0} {$i < 10000} {incr i} {
- while 1 {
- # Make sure all the weights are different because
- # Redis does not use a stable sort but Tcl does.
- randpath {
- set rint [expr int(rand()*1000000)]
- } {
- set rint [expr rand()]
- }
- if {![info exists seenrand($rint)]} break
- }
- set seenrand($rint) x
- r lpush tosort $i
- r sadd tosort-set $i
- r set weight_$i $rint
- r hset wobj_$i weight $rint
- lappend tosort [list $i $rint]
- }
- set sorted [lsort -index 1 -real $tosort]
- for {set i 0} {$i < 10000} {incr i} {
- lappend res [lindex $sorted $i 0]
- }
- format {}
- } {}
-
- test {SORT with BY against the newly created list} {
- r sort tosort {BY weight_*}
- } $res
-
- test {SORT with BY (hash field) against the newly created list} {
- r sort tosort {BY wobj_*->weight}
- } $res
-
- test {SORT with GET (key+hash) with sanity check of each element (list)} {
- set err {}
- set l1 [r sort tosort GET # GET weight_*]
- set l2 [r sort tosort GET # GET wobj_*->weight]
- foreach {id1 w1} $l1 {id2 w2} $l2 {
- set realweight [r get weight_$id1]
- if {$id1 != $id2} {
- set err "ID mismatch $id1 != $id2"
- break
- }
- if {$realweight != $w1 || $realweight != $w2} {
- set err "Weights mismatch! w1: $w1 w2: $w2 real: $realweight"
- break
- }
- }
- set _ $err
- } {}
-
- test {SORT with BY, but against the newly created set} {
- r sort tosort-set {BY weight_*}
- } $res
-
- test {SORT with BY (hash field), but against the newly created set} {
- r sort tosort-set {BY wobj_*->weight}
- } $res
-
- test {SORT with BY and STORE against the newly created list} {
- r sort tosort {BY weight_*} store sort-res
- r lrange sort-res 0 -1
- } $res
+ test "SORT sorted set" {
+ r del zset
+ r zadd zset 1 a
+ r zadd zset 5 b
+ r zadd zset 2 c
+ r zadd zset 10 d
+ r zadd zset 3 e
+ r sort zset alpha desc
+ } {e d c b a}
- test {SORT with BY (hash field) and STORE against the newly created list} {
- r sort tosort {BY wobj_*->weight} store sort-res
- r lrange sort-res 0 -1
- } $res
+ test "SORT sorted set: +inf and -inf handling" {
+ r del zset
+ r zadd zset -100 a
+ r zadd zset 200 b
+ r zadd zset -300 c
+ r zadd zset 1000000 d
+ r zadd zset +inf max
+ r zadd zset -inf min
+ r zrange zset 0 -1
+ } {min c a b d max}
- test {SORT direct, numeric, against the newly created list} {
- r sort tosort
- } [lsort -integer $res]
+ test "SORT regression for issue #19, sorting floats" {
+ r flushdb
+ set floats {1.1 5.10 3.10 7.44 2.1 5.75 6.12 0.25 1.15}
+ foreach x $floats {
+ r lpush mylist $x
+ }
+ assert_equal [lsort -real $floats] [r sort mylist]
+ }
- test {SORT decreasing sort} {
- r sort tosort {DESC}
- } [lsort -decreasing -integer $res]
+ tags {"slow"} {
+ set num 100
+ set res [create_random_dataset $num lpush]
- test {SORT speed, sorting 10000 elements list using BY, 100 times} {
+ test "SORT speed, $num element list BY key, 100 times" {
set start [clock clicks -milliseconds]
for {set i 0} {$i < 100} {incr i} {
set sorted [r sort tosort {BY weight_* LIMIT 0 10}]
set elapsed [expr [clock clicks -milliseconds]-$start]
puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
flush stdout
- format {}
- } {}
+ }
- test {SORT speed, as above but against hash field} {
+ test "SORT speed, $num element list BY hash field, 100 times" {
set start [clock clicks -milliseconds]
for {set i 0} {$i < 100} {incr i} {
set sorted [r sort tosort {BY wobj_*->weight LIMIT 0 10}]
set elapsed [expr [clock clicks -milliseconds]-$start]
puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
flush stdout
- format {}
- } {}
+ }
- test {SORT speed, sorting 10000 elements list directly, 100 times} {
+ test "SORT speed, $num element list directly, 100 times" {
set start [clock clicks -milliseconds]
for {set i 0} {$i < 100} {incr i} {
set sorted [r sort tosort {LIMIT 0 10}]
set elapsed [expr [clock clicks -milliseconds]-$start]
puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
flush stdout
- format {}
- } {}
+ }
- test {SORT speed, pseudo-sorting 10000 elements list, BY <const>, 100 times} {
+ test "SORT speed, $num element list BY <const>, 100 times" {
set start [clock clicks -milliseconds]
for {set i 0} {$i < 100} {incr i} {
set sorted [r sort tosort {BY nokey LIMIT 0 10}]
set elapsed [expr [clock clicks -milliseconds]-$start]
puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds "
flush stdout
- format {}
- } {}
- }
-
- test {SORT regression for issue #19, sorting floats} {
- r flushdb
- foreach x {1.1 5.10 3.10 7.44 2.1 5.75 6.12 0.25 1.15} {
- r lpush mylist $x
}
- r sort mylist
- } [lsort -real {1.1 5.10 3.10 7.44 2.1 5.75 6.12 0.25 1.15}]
-
- test {SORT with GET #} {
- r del mylist
- r lpush mylist 1
- r lpush mylist 2
- r lpush mylist 3
- r mset weight_1 10 weight_2 5 weight_3 30
- r sort mylist BY weight_* GET #
- } {2 1 3}
-
- test {SORT with constant GET} {
- r sort mylist GET foo
- } {{} {} {}}
-
- test {SORT against sorted sets} {
- r del zset
- r zadd zset 1 a
- r zadd zset 5 b
- r zadd zset 2 c
- r zadd zset 10 d
- r zadd zset 3 e
- r sort zset alpha desc
- } {e d c b a}
-
- test {Sorted sets +inf and -inf handling} {
- r del zset
- r zadd zset -100 a
- r zadd zset 200 b
- r zadd zset -300 c
- r zadd zset 1000000 d
- r zadd zset +inf max
- r zadd zset -inf min
- r zrange zset 0 -1
- } {min c a b d max}
+ }
}
} {8}
test {Is the small hash encoded with a zipmap?} {
- r debug object smallhash
- } {*zipmap*}
+ assert_encoding zipmap smallhash
+ }
test {HSET/HLEN - Big hash creation} {
array set bighash {}
} {1024}
test {Is the big hash encoded with a zipmap?} {
- r debug object bighash
- } {*hashtable*}
+ assert_encoding hashtable bighash
+ }
test {HGET against the small hash} {
set err {}
assert_equal 0 [r exists blist1]
}
+ test "$pop: with negative timeout" {
+ set rd [redis_deferring_client]
+ $rd $pop blist1 -1
+ assert_error "ERR*is negative*" {$rd read}
+ }
+
+ test "$pop: with non-integer timeout" {
+ set rd [redis_deferring_client]
+ $rd $pop blist1 1.1
+ assert_error "ERR*not an integer*" {$rd read}
+ }
+
+ test "$pop: with zero timeout should block indefinitely" {
+ # To test this, use a timeout of 0 and wait a second.
+ # The blocking pop should still be waiting for a push.
+ set rd [redis_deferring_client]
+ $rd $pop blist1 0
+ after 1000
+ r rpush blist1 foo
+ assert_equal {blist1 foo} [$rd read]
+ }
+
test "$pop: second argument is not a list" {
set rd [redis_deferring_client]
r del blist1 blist2
}
}
+ test {BLPOP inside a transaction} {
+ r del xlist
+ r lpush xlist foo
+ r lpush xlist bar
+ r multi
+ r blpop xlist 0
+ r blpop xlist 0
+ r blpop xlist 0
+ r exec
+ } {{xlist bar} {xlist foo} {}}
+
test {LPUSHX, RPUSHX - generic} {
r del xlist
assert_equal 0 [r lpushx xlist a]
-start_server {tags {"set"}} {
- test {SADD, SCARD, SISMEMBER, SMEMBERS basics} {
- r sadd myset foo
- r sadd myset bar
- list [r scard myset] [r sismember myset foo] \
- [r sismember myset bar] [r sismember myset bla] \
- [lsort [r smembers myset]]
- } {2 1 1 0 {bar foo}}
-
- test {SADD adding the same element multiple times} {
- r sadd myset foo
- r sadd myset foo
- r sadd myset foo
- r scard myset
- } {2}
+start_server {
+ tags {"set"}
+ overrides {
+ "set-max-intset-entries" 512
+ }
+} {
+ proc create_set {key entries} {
+ r del $key
+ foreach entry $entries { r sadd $key $entry }
+ }
+
+ test {SADD, SCARD, SISMEMBER, SMEMBERS basics - regular set} {
+ create_set myset {foo}
+ assert_encoding hashtable myset
+ assert_equal 1 [r sadd myset bar]
+ assert_equal 0 [r sadd myset bar]
+ assert_equal 2 [r scard myset]
+ assert_equal 1 [r sismember myset foo]
+ assert_equal 1 [r sismember myset bar]
+ assert_equal 0 [r sismember myset bla]
+ assert_equal {bar foo} [lsort [r smembers myset]]
+ }
+
+ test {SADD, SCARD, SISMEMBER, SMEMBERS basics - intset} {
+ create_set myset {17}
+ assert_encoding intset myset
+ assert_equal 1 [r sadd myset 16]
+ assert_equal 0 [r sadd myset 16]
+ assert_equal 2 [r scard myset]
+ assert_equal 1 [r sismember myset 16]
+ assert_equal 1 [r sismember myset 17]
+ assert_equal 0 [r sismember myset 18]
+ assert_equal {16 17} [lsort [r smembers myset]]
+ }
test {SADD against non set} {
r lpush mylist foo
- catch {r sadd mylist bar} err
- format $err
- } {ERR*kind*}
-
- test {SREM basics} {
- r sadd myset ciao
- r srem myset foo
- lsort [r smembers myset]
- } {bar ciao}
-
- test {Mass SADD and SINTER with two sets} {
- for {set i 0} {$i < 1000} {incr i} {
+ assert_error ERR*kind* {r sadd mylist bar}
+ }
+
+ test "SADD a non-integer against an intset" {
+ create_set myset {1 2 3}
+ assert_encoding intset myset
+ assert_equal 1 [r sadd myset a]
+ assert_encoding hashtable myset
+ }
+
+ test "SADD an integer larger than 64 bits" {
+ create_set myset {213244124402402314402033402}
+ assert_encoding hashtable myset
+ assert_equal 1 [r sismember myset 213244124402402314402033402]
+ }
+
+ test "SADD overflows the maximum allowed integers in an intset" {
+ r del myset
+ for {set i 0} {$i < 512} {incr i} { r sadd myset $i }
+ assert_encoding intset myset
+ assert_equal 1 [r sadd myset 512]
+ assert_encoding hashtable myset
+ }
+
+ test "Set encoding after DEBUG RELOAD" {
+ r del myintset myhashset mylargeintset
+ for {set i 0} {$i < 100} {incr i} { r sadd myintset $i }
+ for {set i 0} {$i < 1280} {incr i} { r sadd mylargeintset $i }
+ for {set i 0} {$i < 256} {incr i} { r sadd myhashset [format "i%03d" $i] }
+ assert_encoding intset myintset
+ assert_encoding hashtable mylargeintset
+ assert_encoding hashtable myhashset
+
+ r debug reload
+ assert_encoding intset myintset
+ assert_encoding hashtable mylargeintset
+ assert_encoding hashtable myhashset
+ }
+
+ test {SREM basics - regular set} {
+ create_set myset {foo bar ciao}
+ assert_encoding hashtable myset
+ assert_equal 0 [r srem myset qux]
+ assert_equal 1 [r srem myset foo]
+ assert_equal {bar ciao} [lsort [r smembers myset]]
+ }
+
+ test {SREM basics - intset} {
+ create_set myset {3 4 5}
+ assert_encoding intset myset
+ assert_equal 0 [r srem myset 6]
+ assert_equal 1 [r srem myset 4]
+ assert_equal {3 5} [lsort [r smembers myset]]
+ }
+
+ foreach {type} {hashtable intset} {
+ for {set i 1} {$i <= 5} {incr i} {
+ r del [format "set%d" $i]
+ }
+ for {set i 0} {$i < 200} {incr i} {
r sadd set1 $i
- r sadd set2 [expr $i+995]
+ r sadd set2 [expr $i+195]
+ }
+ foreach i {199 195 1000 2000} {
+ r sadd set3 $i
+ }
+ for {set i 5} {$i < 200} {incr i} {
+ r sadd set4 $i
+ }
+ r sadd set5 0
+
+ # To make sure the sets are encoded as the type we are testing -- also
+ # when the VM is enabled and the values may be swapped in and out
+ # while the tests are running -- an extra element is added to every
+ # set that determines its encoding.
+ set large 200
+ if {$type eq "hashtable"} {
+ set large foo
}
- lsort [r sinter set1 set2]
- } {995 996 997 998 999}
- test {SUNION with two sets} {
- lsort [r sunion set1 set2]
- } [lsort -uniq "[r smembers set1] [r smembers set2]"]
+ for {set i 1} {$i <= 5} {incr i} {
+ r sadd [format "set%d" $i] $large
+ }
- test {SINTERSTORE with two sets} {
- r sinterstore setres set1 set2
- lsort [r smembers setres]
- } {995 996 997 998 999}
+ test "Generated sets must be encoded as $type" {
+ for {set i 1} {$i <= 5} {incr i} {
+ assert_encoding $type [format "set%d" $i]
+ }
+ }
- test {SINTERSTORE with two sets, after a DEBUG RELOAD} {
- r debug reload
- r sinterstore setres set1 set2
- lsort [r smembers setres]
- } {995 996 997 998 999}
+ test "SINTER with two sets - $type" {
+ assert_equal [list 195 196 197 198 199 $large] [lsort [r sinter set1 set2]]
+ }
- test {SUNIONSTORE with two sets} {
- r sunionstore setres set1 set2
- lsort [r smembers setres]
- } [lsort -uniq "[r smembers set1] [r smembers set2]"]
+ test "SINTERSTORE with two sets - $type" {
+ r sinterstore setres set1 set2
+ assert_encoding $type setres
+ assert_equal [list 195 196 197 198 199 $large] [lsort [r smembers setres]]
+ }
+
+ test "SINTERSTORE with two sets, after a DEBUG RELOAD - $type" {
+ r debug reload
+ r sinterstore setres set1 set2
+ assert_encoding $type setres
+ assert_equal [list 195 196 197 198 199 $large] [lsort [r smembers setres]]
+ }
+
+ test "SUNION with two sets - $type" {
+ set expected [lsort -uniq "[r smembers set1] [r smembers set2]"]
+ assert_equal $expected [lsort [r sunion set1 set2]]
+ }
+
+ test "SUNIONSTORE with two sets - $type" {
+ r sunionstore setres set1 set2
+ assert_encoding $type setres
+ set expected [lsort -uniq "[r smembers set1] [r smembers set2]"]
+ assert_equal $expected [lsort [r smembers setres]]
+ }
- test {SUNIONSTORE against non existing keys} {
+ test "SINTER against three sets - $type" {
+ assert_equal [list 195 199 $large] [lsort [r sinter set1 set2 set3]]
+ }
+
+ test "SINTERSTORE with three sets - $type" {
+ r sinterstore setres set1 set2 set3
+ assert_equal [list 195 199 $large] [lsort [r smembers setres]]
+ }
+
+ test "SUNION with non existing keys - $type" {
+ set expected [lsort -uniq "[r smembers set1] [r smembers set2]"]
+ assert_equal $expected [lsort [r sunion nokey1 set1 set2 nokey2]]
+ }
+
+ test "SDIFF with two sets - $type" {
+ assert_equal {0 1 2 3 4} [lsort [r sdiff set1 set4]]
+ }
+
+ test "SDIFF with three sets - $type" {
+ assert_equal {1 2 3 4} [lsort [r sdiff set1 set4 set5]]
+ }
+
+ test "SDIFFSTORE with three sets - $type" {
+ r sdiffstore setres set1 set4 set5
+ # The type is determined by type of the first key to diff against.
+ # See the implementation for more information.
+ assert_encoding $type setres
+ assert_equal {1 2 3 4} [lsort [r smembers setres]]
+ }
+ }
+
+ test "SINTER against non-set should throw error" {
+ r set key1 x
+ assert_error "ERR*wrong kind*" {r sinter key1 noset}
+ }
+
+ test "SUNION against non-set should throw error" {
+ r set key1 x
+ assert_error "ERR*wrong kind*" {r sunion key1 noset}
+ }
+
+ test "SINTERSTORE against non existing keys should delete dstkey" {
r set setres xxx
- list [r sunionstore setres foo111 bar222] [r exists xxx]
- } {0 0}
-
- test {SINTER against three sets} {
- r sadd set3 999
- r sadd set3 995
- r sadd set3 1000
- r sadd set3 2000
- lsort [r sinter set1 set2 set3]
- } {995 999}
-
- test {SINTERSTORE with three sets} {
- r sinterstore setres set1 set2 set3
- lsort [r smembers setres]
- } {995 999}
-
- test {SUNION with non existing keys} {
- lsort [r sunion nokey1 set1 set2 nokey2]
- } [lsort -uniq "[r smembers set1] [r smembers set2]"]
-
- test {SDIFF with two sets} {
- for {set i 5} {$i < 1000} {incr i} {
- r sadd set4 $i
+ assert_equal 0 [r sinterstore setres foo111 bar222]
+ assert_equal 0 [r exists setres]
+ }
+
+ test "SUNIONSTORE against non existing keys should delete dstkey" {
+ r set setres xxx
+ assert_equal 0 [r sunionstore setres foo111 bar222]
+ assert_equal 0 [r exists setres]
+ }
+
+ foreach {type contents} {hashtable {a b c} intset {1 2 3}} {
+ test "SPOP basics - $type" {
+ create_set myset $contents
+ assert_encoding $type myset
+ assert_equal $contents [lsort [list [r spop myset] [r spop myset] [r spop myset]]]
+ assert_equal 0 [r scard myset]
}
- lsort [r sdiff set1 set4]
- } {0 1 2 3 4}
- test {SDIFF with three sets} {
- r sadd set5 0
- lsort [r sdiff set1 set4 set5]
- } {1 2 3 4}
+ test "SRANDMEMBER - $type" {
+ create_set myset $contents
+ unset -nocomplain myset
+ array set myset {}
+ for {set i 0} {$i < 100} {incr i} {
+ set myset([r srandmember myset]) 1
+ }
+ assert_equal $contents [lsort [array names myset]]
+ }
+ }
- test {SDIFFSTORE with three sets} {
- r sdiffstore sres set1 set4 set5
- lsort [r smembers sres]
- } {1 2 3 4}
+ proc setup_move {} {
+ r del myset3 myset4
+ create_set myset1 {1 a b}
+ create_set myset2 {2 3 4}
+ assert_encoding hashtable myset1
+ assert_encoding intset myset2
+ }
- test {SPOP basics} {
- r del myset
- r sadd myset 1
- r sadd myset 2
- r sadd myset 3
- list [lsort [list [r spop myset] [r spop myset] [r spop myset]]] [r scard myset]
- } {{1 2 3} 0}
-
- test {SRANDMEMBER} {
- r del myset
- r sadd myset a
- r sadd myset b
- r sadd myset c
- unset -nocomplain myset
- array set myset {}
- for {set i 0} {$i < 100} {incr i} {
- set myset([r srandmember myset]) 1
- }
- lsort [array names myset]
- } {a b c}
-
- test {SMOVE basics} {
- r sadd myset1 a
- r sadd myset1 b
- r sadd myset1 c
- r sadd myset2 x
- r sadd myset2 y
- r sadd myset2 z
- r smove myset1 myset2 a
- list [lsort [r smembers myset2]] [lsort [r smembers myset1]]
- } {{a x y z} {b c}}
-
- test {SMOVE non existing key} {
- list [r smove myset1 myset2 foo] [lsort [r smembers myset2]] [lsort [r smembers myset1]]
- } {0 {a x y z} {b c}}
-
- test {SMOVE non existing src set} {
- list [r smove noset myset2 foo] [lsort [r smembers myset2]]
- } {0 {a x y z}}
-
- test {SMOVE non existing dst set} {
- list [r smove myset2 myset3 y] [lsort [r smembers myset2]] [lsort [r smembers myset3]]
- } {1 {a x z} y}
-
- test {SMOVE wrong src key type} {
+ test "SMOVE basics - from regular set to intset" {
+ # move a non-integer element to an intset should convert encoding
+ setup_move
+ assert_equal 1 [r smove myset1 myset2 a]
+ assert_equal {1 b} [lsort [r smembers myset1]]
+ assert_equal {2 3 4 a} [lsort [r smembers myset2]]
+ assert_encoding hashtable myset2
+
+ # move an integer element should not convert the encoding
+ setup_move
+ assert_equal 1 [r smove myset1 myset2 1]
+ assert_equal {a b} [lsort [r smembers myset1]]
+ assert_equal {1 2 3 4} [lsort [r smembers myset2]]
+ assert_encoding intset myset2
+ }
+
+ test "SMOVE basics - from intset to regular set" {
+ setup_move
+ assert_equal 1 [r smove myset2 myset1 2]
+ assert_equal {1 2 a b} [lsort [r smembers myset1]]
+ assert_equal {3 4} [lsort [r smembers myset2]]
+ }
+
+ test "SMOVE non existing key" {
+ setup_move
+ assert_equal 0 [r smove myset1 myset2 foo]
+ assert_equal {1 a b} [lsort [r smembers myset1]]
+ assert_equal {2 3 4} [lsort [r smembers myset2]]
+ }
+
+ test "SMOVE non existing src set" {
+ setup_move
+ assert_equal 0 [r smove noset myset2 foo]
+ assert_equal {2 3 4} [lsort [r smembers myset2]]
+ }
+
+ test "SMOVE from regular set to non existing destination set" {
+ setup_move
+ assert_equal 1 [r smove myset1 myset3 a]
+ assert_equal {1 b} [lsort [r smembers myset1]]
+ assert_equal {a} [lsort [r smembers myset3]]
+ assert_encoding hashtable myset3
+ }
+
+ test "SMOVE from intset to non existing destination set" {
+ setup_move
+ assert_equal 1 [r smove myset2 myset3 2]
+ assert_equal {3 4} [lsort [r smembers myset2]]
+ assert_equal {2} [lsort [r smembers myset3]]
+ assert_encoding intset myset3
+ }
+
+ test "SMOVE wrong src key type" {
r set x 10
- catch {r smove x myset2 foo} err
- format $err
- } {ERR*}
+ assert_error "ERR*wrong kind*" {r smove x myset2 foo}
+ }
- test {SMOVE wrong dst key type} {
+ test "SMOVE wrong dst key type" {
r set x 10
- catch {r smove myset2 x foo} err
- format $err
- } {ERR*}
+ assert_error "ERR*wrong kind*" {r smove myset2 x foo}
+ }
}
-# redis-sha1.rb - Copyright (C) 2009 Salvatore Sanfilippo
+# redis-copy.rb - Copyright (C) 2009-2010 Salvatore Sanfilippo
# BSD license, See the COPYING file for more information.
#
-# Performs the SHA1 sum of the whole datset.
-# This is useful to spot bugs in persistence related code and to make sure
-# Slaves and Masters are in SYNC.
+# Copy the whole dataset from one Redis instance to another one
#
-# If you hack this code make sure to sort keys and set elements as this are
-# unsorted elements. Otherwise the sum may differ with equal dataset.
+# WARNING: currently hashes and sorted sets are not supported! This
+# program should be updated.
require 'rubygems'
require 'redis'
then
echo -n "$PIDFILE does not exist, process is not running\n"
else
+ PID=$(cat $PIDFILE)
echo -n "Stopping ...\n"
- echo -n "Sending SHUTDOWN\r\n" | nc localhost $REDISPORT &
- PID=$(cat $PIDFILE)
+ echo -n "SHUTDOWN\r\n" | nc localhost $REDISPORT &
while [ -x /proc/${PIDFILE} ]
do
echo "Waiting for Redis to shutdown ..."
sleep 1
done
- rm $PIDFILE
echo "Redis stopped"
fi
;;