1 module redis.redis;
2 
3 import std.stdio;
4 import std.conv;
5 import std.traits;
6 import std.json;
7 import std.exception;
8 import std.variant;
9 import std.array;
10 import std..string;
11 import std.typetuple;
12 import std.exception;
13 
14 public import redis;
15 import std.socket;
16 public class Redis
17 {
18 	import std.socket : TcpSocket, InternetAddress;
19 
20 	private:
21 	TcpSocket[string] conns;
22 	string addr;
23 	string password;
24 	public:
25 
26 	mixin keyCommands;
27 	mixin stringCommands;
28 	mixin hashCommands;
29 	mixin listCommands;
30 	mixin setCommands;
31 	mixin sortedsetCommands;
32 	mixin serverCommands;
33 
34 	/**
35 	 * Create a new connection to the Redis server
36 	 */
37 	this(string host = "127.0.0.1", ushort port = 6379,string password = "")
38 	{
39 		auto conn = new TcpSocket(new InternetAddress(host, port));
40 		addr = host~":"~to!string(port);
41 		conns[addr] = conn; 
42 		writeln(password);
43         writeln("AUTH "~password);
44 		this.password = password;
45         if(password.length > 0){
46 			conn.send(toMultiBulk("AUTH", password));
47 		    Response[] r = receiveResponses(conn, 1);
48         }
49 	}
50 
51 	override string toString()
52 	{
53 		return "redis";
54 	}
55 
56 	auto opDispatch(string name,T...)(T args)
57 	{
58 		//try{
59 		auto result =  send(name,args);
60 		//} catch (exception e){
61 		//return e.msg;
62 		//}
63 		return result;
64 	}
65 
66 	/**
67 	 * Call Redis using any type T that can be converted to a string
68 	 *
69 	 * Examples:
70 	 *
71 	 * ---
72 	 * send("SET name Adil")
73 	 * send("SADD", "myset", 1)
74 	 * send("SADD", "myset", 1.2)
75 	 * send("SADD", "myset", true)
76 	 * send("SADD", "myset", "Batman")
77 	 * send("SREM", "myset", ["$3", "$4"])
78 	 * send("SADD", "myset", object) //provided 'object' implements toString()
79 	 * send("GET", "*") == send("GET *")
80 	 * send("ZADD", "my_unique_json", 1, json.toString());
81 	 * send("EVAL", "return redis.call('set','lua','LUA')", 0);
82 	 * ---
83 	 */
84 	R send(R = Response, T...)(string key, T args)
85 	{
86 		//Implement a write queue here.
87 		// All encoded responses are put into a write queue and flushed
88 		// For a send request, flush the queue and listen to a response
89 		// For async calls, just flush the queue
90 		// This automatically gives us PubSub
91 
92 		SENDS:
93 		debug(redis) { writeln(__FUNCTION__,"\t",__LINE__,escape(toMultiBulk(key, args)));}
94 		conn.send(toMultiBulk(key, args));
95 		Response[] r = receiveResponses(conn, 1);
96 		if(r.length && r[0].isMoved)
97 		{
98 			addr = ((split(r[0].toString," "))[2]);
99 			goto SENDS;
100 		}
101 		return cast(R)(r[0]);
102 	}
103 
104 	R send(R = Response)(string cmd)
105 	{
106 		SEND:
107 		debug(redis) { writeln(escape(toMultiBulk(cmd)));}
108 		conn.send(toMultiBulk(cmd));
109 		Response[] r = receiveResponses(conn, 1);
110 		if(r.length && r[0].isMoved)
111 		{
112 			addr = ((split(r[0].toString," "))[2]);
113 			goto SEND;
114 		}
115 		return cast(R)(r[0]);
116 	}
117 
118 	/**
119 	 * Send a string that is already encoded in the Redis protocol
120 	 */
121 	R sendRaw(R = Response)(string cmd)
122 	{
123 		SEND:
124 
125 		debug(redis) { writeln(escape(cmd));}
126 
127 		conn.send(cmd);
128 		Response[] r = receiveResponses(conn, 1);
129 		if(r.length && r[0].isMoved)
130 		{
131 			addr = ((split(r[0].toString," "))[2]);
132 			goto SEND;
133 		}
134 		return cast(R)(r[0]);
135 	}
136 
137 	/**
138 	 * Send a series of commands as a pipeline
139 	 *
140 	 * Examples:
141 	 *
142 	 * ---
143 	 * pipeline(["SADD shopping_cart Shirt", "SADD shopping_cart Pant", "SADD shopping_cart Boots"])
144 	 * ---
145 	 */
146 	import std.traits : isSomeChar;
147 	Response[] pipeline(C)(C[][] commands) if (isSomeChar!C)
148 	{
149 		import std.array : appender;
150 
151 		auto app = appender!(C[])();
152 		foreach(c; commands) {
153 			app ~= encode(c);
154 		}
155 
156 		conn.send(app.data);
157 		return receiveResponses(conn, commands.length);
158 	}
159 
160 	/**
161 	 * Execute commands in a MULTI/EXEC block.
162 	 *
163 	 * @param all - (Default: false) - By default, only the results of a transaction are returned. If set to "true", the results of each queuing step is also returned.
164 	 *
165 	 * Examples:
166 	 *
167 	 * ---
168 	 * transaction(["SADD shopping_cart Shirt", "INCR shopping_cart_ctr"])
169 	 * ---
170 	 */
171 	Response[] transaction(string[] commands, bool all = false)
172 	{
173 		auto cmd = ["MULTI"];
174 		cmd ~= commands;
175 		cmd ~= "EXEC";
176 		auto rez = pipeline(cmd);
177 
178 		if(all) {
179 			return rez;
180 		}
181 
182 		auto resp = rez[$ - 1];
183 		if(resp.isError()) {
184 			throw new RedisResponseException(resp.value);
185 		}
186 
187 		return resp.values;
188 	}
189 
190 	/**
191 	 * Simplified call to EVAL
192 	 *
193 	 * Examples:
194 	 *
195 	 * ---
196 	 * Response r = eval("return redis.call('set','lua','LUA_AGAIN')");
197 	 * r.value == "LUA_AGAIN";
198 	 *
199 	 * Response r1 = redis.eval("return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", ["key1", "key2"], ["first", "second"]);
200 	 * writeln(r1); // [key1, key2, first, second]
201 	 *
202 	 * Response r1 = redis.eval("return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", [1, 2]);
203 	 * writeln(r1); // [1, 2]
204 	 * ---
205 	 */
206 	Response eval(K = string, A = string)(string lua_script, K[] keys = [], A[] args = [])
207 	{
208 		conn.send(toMultiBulk("EVAL", lua_script, keys.length, keys, args));
209 		Response[] r = receiveResponses(conn, 1);
210 		return (r[0]);
211 	}
212 
213 	Response evalSha(K = string, A = string)(string sha1, K[] keys = [], A[] args = [])
214 	{
215 		conn.send(toMultiBulk("EVALSHA", sha1, keys.length, keys, args));
216 		Response[] r = receiveResponses(conn, 1);
217 		return (r[0]);
218 	}
219 
220 	TcpSocket conn()
221 	{
222 		if(addr !in conns){
223 			auto arr = split(addr,":");
224 			conns[addr] = new TcpSocket(new InternetAddress(arr[0], arr[1].to!ushort));
225 			if(password.length > 0){
226 				conns[addr].send(toMultiBulk("AUTH", password));
227 				Response[] r = receiveResponses(conns[addr], 1);
228 			}
229 		}
230 
231 		int error;
232 		conns[addr].getOption(SocketOptionLevel.SOCKET, SocketOption.ERROR, error);
233 		if (error != 0)
234 		{
235 			conns[addr].close();
236 			auto arr = split(addr,":");
237 			conns[addr] = new TcpSocket(new InternetAddress(arr[0], arr[1].to!ushort));
238 			if(password.length > 0){
239 				conns[addr].send(toMultiBulk("AUTH", password));
240 				Response[] r = receiveResponses(conns[addr], 1);
241 			}
242 			throw new ConnectionException("Error while sending request, error " ~ to!string(error));
243 			
244 		}
245 		return conns[addr];
246 	}
247 
248 }
249 
250 
251 
252 unittest
253 {
254 	auto redis = new Redis();
255 	//debug(redis) { writeln("\n\n\nredis commands test.....");}
256 	//assert(redis.ping() == "PONG");
257 	redis.flushall();
258 	assert(redis.set("xxkey","10") == true);
259 	assert(redis.get("xxkey") == "10");
260 	assert(redis.exists("xxkey") == true);
261 	//assert(redis.dump("xxkey") == "\u0000\xC0\n\b\u0000ײ\xBB\xFA\xA7\xB7\xE9\x83");
262 	assert(redis.del("xxkey") == 1);
263 	assert(redis.set("xxkey","10") == true);
264 	assert(redis.expire("xxkey",20) == 1);
265 	assert(redis.expire("yykey",20) == 0);
266 	assert(redis.expireat("xxkey",1798736461) == 1);
267 	assert(redis.expireat("yykey",1798736461) == 0);
268 	redis.set("ssoxx","1");
269 	redis.set("bboxx","2");
270 	redis.set("ssmxx","3");
271 	assert(redis.keys("*m*") == ["ssmxx"]);
272 	redis.set("abc","test",10);
273 	assert(redis.ttl("abc") == 10);
274 	redis.set("incrkey","10");
275 	assert(redis.incr("incrkey") == 11);
276 
277 	assert(redis.hset("website","google","google.com") == 1);
278 	assert(redis.hset("website","baidu","baidu.com") == 1);
279 	assert(redis.hset("website","putao","putao.com") == 1);
280 	assert(redis.hset("website","google","google.com") == 0);
281 	assert(redis.hlen("website") == 3);
282 	assert(redis.hget("website","google") == "google.com");
283 	assert(redis.hdel("website","google") == 1);
284 	assert(redis.hlen("website") == 2);
285 	assert(redis.hdel("website","baidu","putao") == 2);
286 	assert(redis.hlen("website") == 0);
287 
288 	assert(redis.lpush("language","c") == 1);
289 	assert(redis.lpush("language","php") == 2);
290 	assert(redis.llen("language") == 2);
291 	assert(redis.lset("language",0,"d") == true);
292 	assert(redis.lpop("language") == "d");
293 	assert(redis.llen("language") == 1);
294 	assert(redis.lrange("language",0,1) == ["c"]);
295 
296 	assert(redis.sadd("bbs","baidu.com","google.com") == 2);
297 	assert(redis.sadd("bbs","douban.com") == 1);
298 	assert(redis.scard("bbs") == 3);
299 	assert(redis.sadd("net","douban.com") == 1);
300 	assert(redis.spop("net") == "douban.com");
301 
302 	debug(redis){writeln("redis commands test end\n\n\n");}
303 
304 	auto response = redis.send("LASTSAVE");
305 	assert(response.type == ResponseType.Integer);
306 
307 	assert(redis.send!(bool)("SET", "name", "adil baig"));
308 
309 	redis.send("SET emptystring ''");
310 	response = redis.send("GET emptystring");
311 	assert(response.value == "");
312 
313 	response = redis.send("GET name");
314 	assert(response.type == ResponseType.Bulk);
315 	assert(response.value == "adil baig");
316 
317 	/* START Test casting byte[] */
318 	assert(cast(byte[])response == "adil baig"); //Test casting to byte[]
319 	assert(cast(byte[])response == [97, 100, 105, 108, 32, 98, 97, 105, 103]);
320 
321 	redis.send("SET mykey 10");
322 	response = redis.send("INCR mykey");
323 	assert(response.type == ResponseType.Integer);
324 	assert(response.intval == 11);
325 	auto bytes = (cast(ubyte[])response);
326 	assert(bytes.length == response.intval.sizeof);
327 	assert(bytes[0] == 11);
328 	/* END Test casting byte[] */
329 
330 	assert(redis.send!(string)("GET name") == "adil baig");
331 
332 	response = redis.send("GET nonexistentkey");
333 	assert(response.type == ResponseType.Nil);
334 	assert(cast(ubyte[])response == []);
335 
336 	redis.send("DEL myset");
337 	redis.send("SADD", "myset", 1.2);
338 	redis.send("SADD", "myset", 1);
339 	redis.send("SADD", "myset", true);
340 	redis.send("SADD", "myset", "adil");
341 	redis.send("SADD", "myset", 350001939);
342 	redis.send("SADD", ["myset","$4"]);
343 	auto r = redis.send("SMEMBERS myset");
344 	assert(r.type == ResponseType.MultiBulk);
345 	assert(r.values.length == 6);
346 
347 	//Check pipeline
348 	redis.send("DEL ctr");
349 	auto responses = redis.pipeline(["SET ctr 1", "INCR ctr", "INCR ctr", "INCR ctr", "INCR ctr"]);
350 
351 	assert(responses.length == 5);
352 	assert(responses[0].type == ResponseType.Status);
353 	assert(responses[1].intval == 2);
354 	assert(responses[2].intval == 3);
355 	assert(responses[3].intval == 4);
356 	assert(responses[4].intval == 5);
357 
358 	redis.send("DEL buddies");
359 	auto buddiesQ = ["SADD buddies Batman", "SADD buddies Spiderman", "SADD buddies Hulk", "SMEMBERS buddies"];
360 	Response[] buddies = redis.pipeline(buddiesQ);
361 	assert(buddies.length == buddiesQ.length);
362 	assert(buddies[0].type == ResponseType.Integer);
363 	assert(buddies[1].type == ResponseType.Integer);
364 	assert(buddies[2].type == ResponseType.Integer);
365 	assert(buddies[3].type == ResponseType.MultiBulk);
366 	assert(buddies[3].values.length == 3);
367 
368 	//Check transaction
369 	redis.send("DEL ctr");
370 	responses = redis.transaction(["SET ctr 1", "INCR ctr", "INCR ctr"], true);
371 	assert(responses.length == 5);
372 	assert(responses[0].type == ResponseType.Status);
373 	assert(responses[1].type == ResponseType.Status);
374 	assert(responses[2].type == ResponseType.Status);
375 	assert(responses[3].type == ResponseType.Status);
376 	assert(responses[4].type == ResponseType.MultiBulk);
377 	assert(responses[4].values[0].type == ResponseType.Status);
378 	assert(responses[4].values[1].intval == 2);
379 	assert(responses[4].values[2].intval == 3);
380 
381 	redis.send("DEL ctr");
382 	responses = redis.transaction(["SET ctr 1", "INCR ctr", "INCR ctr"]);
383 	assert(responses.length == 3);
384 	assert(responses[0].type == ResponseType.Status);
385 	assert(responses[1].intval == 2);
386 	assert(responses[2].intval == 3);
387 
388 	response = redis.send("EVAL", "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", 2, "key1", "key2", "first", "second");
389 	assert(response.values.length == 4);
390 	assert(response.values[0].value == "key1");
391 	assert(response.values[1].value == "key2");
392 	assert(response.values[2].value == "first");
393 	assert(response.values[3].value == "second");
394 
395 	//Same as above, but simpler
396 	response = redis.eval("return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", ["key1", "key2"], ["first", "second"]);
397 	assert(response.values.length == 4);
398 	assert(response.values[0].value == "key1");
399 	assert(response.values[1].value == "key2");
400 	assert(response.values[2].value == "first");
401 	assert(response.values[3].value == "second");
402 
403 	response = redis.eval("return redis.call('set','lua','LUA_AGAIN')");
404 	assert(cast(string)redis.send("GET lua") == "LUA_AGAIN");
405 
406 	// A BLPOP times out to a Nil multibulk
407 	response = redis.send("BLPOP nonExistentList 1");
408 	assert(response.isNil());
409 }