1 module drocks.client;
2 
3 import std.range    : join, isInputRange, ElementType;
4 import std.traits   : isIntegral;
5 import std.typecons : Tuple, tuple;
6 import std.array    : byPair;
7 import std.conv     : to;
8 
9 import drocks.exception : ClientException;
10 import drocks.request   : Request;
11 import drocks.pair      : Pair;
12 import drocks.backup    : BackupUnitsRange;
13 
14 struct Client
15 {
16 private:
17     Request _req;
18 
19 public:
20     this(string host, ushort port)
21     {
22         _req = Request(host, port);
23     }
24     @disable this ();
25 
26     static Client createDefault()
27     {
28         return Client("localhost", 5533);
29     }
30 
31     ref Request request()
32     {
33         return _req;
34     }
35 
36     // get value by key
37     string get(string key)
38     {
39         return _req.httpGet("get", key).getValue();
40     }
41 
42     // multi get key-value pairs by keys
43     auto get(Range)(auto ref Range range)
44         if(isInputRange!Range && is(ElementType!Range == string))
45     {
46         return _req.httpGet("mget", range).getMultiPair();
47     }
48 
49     auto get(Args...)(auto ref Args args)
50         if(Args.length > 1 && is(Args[0] == string))
51     {
52         return _req.httpGet("mget", args).getMultiPair();
53     }
54 
55     // set value for key
56     bool set(string key, string val)
57     {
58         return this.set(Pair(key, val));
59     }
60 
61     bool set(Pair pair)
62     {
63         return _req.httpPost("set", pair.serialize()).isOk();
64     }
65 
66     // multi set values for keys
67     bool set(Range)(auto ref Range pairs)
68         if(isInputRange!Range && is(ElementType!Range == Pair))
69     {
70         return _req.httpPost("mset", pairs).isOk();
71     }
72 
73     bool set(Range)(auto ref Range pairs)
74         if(isInputRange!Range && is(ElementType!Range: Tuple!(string, string)))
75     {
76         return _req.httpPost("mset", pairs).isOk();
77     }
78 
79     bool set(string[string] pairs)
80     {
81         return _req.httpPost("mset", pairs.byPair()).isOk();
82     }
83 
84     // remove key from db
85     bool del(string key)
86     {
87         return _req.httpPost("del", key).isOk();
88     }
89 
90     // Multi range of keys from db
91     bool del(Range)(auto ref Range keys)
92         if(isInputRange!Range && is(ElementType!Range == string))
93     {
94         return _req.httpPost("mdel", keys).isOk();
95     }
96 
97     auto del(Args...)(auto ref Args args)
98         if(Args.length > 1 && is(Args[0] == string))
99     {
100         return _req.httpPost("mdel", args).isOk();
101     }
102 
103     // Increment value by key
104     bool incr(string key, long value)
105     {
106         return _req.httpPost("incr", key, value ).isOk();
107     }
108     
109     bool incr(string key)
110     {
111         return _req.httpPost("incr", key).isOk();
112     }
113 
114     // multi get all key-value pairs (by key-prefix)
115     auto getall(string prefix)
116     {
117         return _req.httpGet("prefit", prefix).getMultiPair();
118     }
119 
120     // multi get all key-value pairs
121     auto getall()
122     {
123         return _req.httpGet("tail").getMultiPair();
124     }
125 
126     // multi get key-value pairs by seek key
127     auto seekPrev(string prefixStart)
128     {
129         // GET /seekprev?<key-prefix-start>
130         return _req.httpGet("seekprev", prefixStart).getMultiPair();
131     }
132     auto seekPrev(string prefixStart, string startsWith)
133     {
134         // GET /seekprev?<key-prefix-start>&<starts-with>
135         return _req.httpGet("seekprev", prefixStart, startsWith).getMultiPair();
136     }
137     auto seekPrevRange(string prefixStart, string prefixEnd)
138     {
139         // GET /seekprev-range?<key-prefix-start>&<key-prefix-end>
140         return _req.httpGet("seekprev-range", prefixStart, prefixEnd).getMultiPair();
141     }
142     auto seekPrevRange(string prefixStart, string prefixEnd, string startsWith)
143     {
144         // GET /seekprev-range?<key-prefix-start>&<key-prefix-end>&<starts-with>
145         return _req.httpGet("seekprev-range", prefixStart, prefixEnd, startsWith).getMultiPair();
146     }
147 
148     auto seekNext(string prefixStart)
149     {
150         // GET /seeknext?<key-prefix-start>
151         return _req.httpGet("seeknext", prefixStart).getMultiPair();
152     }
153     auto seekNext(string prefixStart, string startsWith)
154     {
155         // GET /seeknext?<key-prefix-start>&<starts-with>
156         return _req.httpGet("seeknext", prefixStart, startsWith).getMultiPair();
157     }
158     auto seekNextRange(string prefixStart, string prefixEnd)
159     {
160         // GET /seeknext-range?<key-prefix-start>&<key-prefix-end>
161         return _req.httpGet("seeknext-range", prefixStart, prefixEnd).getMultiPair();
162     }
163     auto seekNextRange(string prefixStart, string prefixEnd, string startsWith)
164     {
165         // GET /seeknext-range?<key-prefix-start>&<key-prefix-end>&<starts-with>
166         return _req.httpGet("seeknext-range", prefixStart, prefixEnd, startsWith).getMultiPair();
167     }
168 
169     // Make database backup
170     bool backup()
171     {
172         return _req.httpPost("backup").isOk();
173     }
174 
175     // Make database backups info
176     auto backupInfo()
177     {
178         return _req.httpPost("backup/info").BackupUnitsRange;
179     }
180 
181     // Remove backup by ID
182     bool backupDel(size_t id)
183     {
184         return _req.httpPost("backup/del", id).isOk;
185     }
186 
187     // Remove backup by IDs
188     auto backupDel(Range)(auto ref Range ids)
189         if(isInputRange!Range && isIntegral!(ElementType!Range))
190     {
191         return _req.httpPost("backup/mdel", ids).getMultiBool();
192     }
193 
194     // retrive server statistics
195     string stats()
196     {
197         return _req.httpPost("stats").raw;
198     }   
199 
200     static struct KeyExist
201     {
202         bool   has;
203         string value;
204         alias has this;
205     }
206     // Check if key exist
207     KeyExist has(string key) {
208         auto resp = _req.httpGet("exist", key);
209         bool rez  = resp.isOk();
210         return !rez ? 
211             KeyExist(rez) :
212             KeyExist(rez, resp.getValue());
213     }
214 
215     //
216     // Implementation array access overloading
217     //
218     // https://dlang.org/spec/operatoroverloading.html
219     // get by db[...]
220     auto opIndex(Args...)(auto ref Args args)
221     {
222         return this.get(args);
223     }
224     // set by db[...] = ...
225     auto opIndexAssign(string val, string key)
226     {
227         return this.set(key, val);
228     }
229     // db[...] += ...
230     bool opIndexOpAssign(string op)(long value, string key)
231         if("+" == op)
232     {
233         return incr(key, value);
234     }
235     // db[...] += ...
236     bool opIndexOpAssign(string op)(long value, string key)
237         if("-" == op)
238     {
239         return incr(key, -value);
240     }
241 
242     // ++db[...]
243     bool opIndexUnary(string op)(string key)
244         if("++" == op)
245     {
246         return incr(key);
247     }
248     // --db[...]
249     bool opIndexUnary(string op)(string key)
250         if("--" == op)
251     {
252         return incr(key, -1);
253     }
254 
255 }
256 
257 //
258 // This mixin allows to extend struct Client
259 // 
260 //Example:
261 //struct CustomClient
262 //{
263 //    mixin ExtendClient;
264 //    string getCastom(string key)
265 //    {
266 //        return _db.request.httpGet("castom", key).getValue();
267 //    }
268 //}
269 //
270 
271 mixin template ExtendClient()
272 {
273 private:
274     alias __ThisType = typeof(this);
275     Client _db;
276 public:
277     alias _db this;
278 
279     this(string host, ushort port)
280     {
281         _db = Client(host, port);
282     }
283     //@disable this ();
284 
285     static __ThisType createDefault()
286     {
287         __ThisType rez = __ThisType.init;
288         rez._db = Client.createDefault();
289         return rez;
290     }
291 
292     auto opDispatch(string methodName, Args...)(auto ref Args args) {
293         return mixin("_db." ~ methodName)(args);
294     }
295 }