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