1 // Written in D programming language
2 /**
3 * This module contain json<->sql table from db.<br>
4 *
5 * Loads on SIGHUP or on startup
6 *
7 * Copyright: © 2014 DSoftOut
8 * License: Subject to the terms of the MIT license, as written in the included LICENSE file.
9 * Authors: Zaramzan <shamyan.roman@gmail.com>
10 *
11 */
12 module server.sql_json;
13 
14 import std.algorithm;
15 import std.traits;
16 import std.conv;
17 
18 import util;
19 
20 /**
21 * Represent sql to json table line from database
22 *
23 * Authors: Zaramzan <shamyan.roman@gmail.com>
24 */
25 struct Entry
26 {
27 	@required
28 	string method;
29 	
30 	@required
31 	string[] sql_queries;
32 	
33 	@required
34 	uint[] arg_nums;
35 	
36 	@required
37 	bool set_username;
38 	
39 	@required
40 	bool need_cache;
41 	
42 	@required
43 	bool read_only;
44 	
45 	@possible
46 	string[] reset_caches;
47 	
48 	@possible
49 	string[] reset_by;
50 	
51 	@possible
52 	string commentary;
53 	
54 	@possible
55 	bool[] result_filter;
56 	
57 	@possible
58 	bool[] one_row_flags;
59 	
60 	const bool isValidParams(in string[] params, out size_t expected)
61 	{
62 	    expected = reduce!"a+b"(0, arg_nums);
63 		return params.length == expected;
64 	}
65 	
66 	const bool isValidFilter(out size_t expected)
67 	{
68 	    expected = sql_queries.length;
69 	    if(!needResultFiltering) return true;
70 	    else return result_filter.length == sql_queries.length;
71 	}
72 	
73 	const bool isValidOneRowConstraint(out size_t expected)
74 	{
75 	    expected = sql_queries.length;
76 	    if(!needOneRowCheck) return true;
77 	    else return one_row_flags.length == sql_queries.length;
78 	}
79 	
80 	const bool needResultFiltering()
81 	{
82 	    return result_filter && result_filter != [];
83 	}
84 	
85 	const bool needOneRowCheck()
86 	{
87 	    return one_row_flags && one_row_flags != [];
88 	}
89 	
90 	const shared(Entry) toShared() @property
91 	{
92 		auto res = this;
93 		
94 		return cast(shared Entry) res;
95 	}
96 	
97 	void toString(scope void delegate(const(char)[]) sink) const
98 	{
99 	    sink("method: "); sink(method); sink("\n");
100 	    sink("set_username: "); sink(set_username.to!string); sink("\n");
101 	    sink("need_cache: "); sink(need_cache.to!string); sink("\n");
102 	    sink("read_only: "); sink(read_only.to!string); sink("\n");
103 	    sink("reset_caches: "); sink(reset_caches.to!string); sink("\n");
104 	    sink("reset_by: "); sink(reset_by.to!string); sink("\n");
105 	    sink("commentary: "); sink(commentary); sink("\n");
106 	    foreach(immutable i, query; sql_queries)
107 	    {
108 	        sink("\t"); sink("query: "); sink(query); sink("\n");
109 	        if(arg_nums && arg_nums != [])
110 	        {
111 	            sink("\t\t"); sink("argnums: "); sink(arg_nums[i].to!string); sink("\n");
112             } else
113             {
114                 sink("\t\targnums: 0\n");
115             }
116             if(result_filter && result_filter != [])
117             {
118                 sink("\t\t"); sink("filter: "); sink(result_filter[i].to!string); sink("\n");
119             } else
120             {
121                 sink("\t\tfilter: true\n");
122             }
123             if(one_row_flags && one_row_flags != [])
124             {
125                 sink("\t\t"); sink("is_one_row: "); sink(one_row_flags[i].to!string); sink("\n");
126             } else
127             {
128                 sink("\t\t"); sink("is_one_row: false \n");
129             }
130         } 
131 	}
132 }
133 
134 /**
135 * Contains methods descriptions, cache rules, etc
136 * 
137 * Authors: Zaramzan <shamyan.roman@gmail.com>
138 */
139 class SqlJsonTable
140 {
141     shared
142     {
143 	
144     	/// Add entry to memory
145     	void add(in Entry entry)
146     	{
147     		map[entry.method] = entry.toShared();
148     	}
149     	
150     	void reset()
151     	{
152     		synchronized(this)
153     		{
154     			foreach(key; map.byKey())
155     			{
156     				map.remove(key);
157     			}
158     		}
159     	}
160     	
161     	/**
162     	* Returns: need_cache flag by method
163     	*/
164     	bool need_cache(string method)
165     	{
166     		auto p = method in map;
167     		
168     		if (p is null) return false;
169     		
170     		auto val = *p;
171     		
172     		return val.need_cache && val.read_only;
173     	}
174     	
175     	/**
176     	* Returns read_only flag by method
177     	*/
178     	bool read_only(string method)
179     	{
180     		auto p = method in map;
181     		
182     		if (p is null) return false;
183     		
184     		auto val = *p;
185     		
186     		return val.read_only;
187     	}
188     	
189     	/**
190     	* Returns: reset_caches array by method
191     	*/
192     	string[] reset_caches(string method)
193     	{
194     		auto p = method in map;
195     		
196     		if (p is null) return null;
197     		
198     		auto val = *p;
199     		
200     		return cast(string[])(val.reset_caches);
201     	}
202     	
203     	/**
204     	* Returns: reset_by array by method
205     	*/
206     	string[] reset_by(string method)
207     	{
208     		auto p = method in map;
209     		
210     		if (p is null) return null;
211     		
212     		auto val = *p;
213     		
214     		return cast(string[])(val.reset_by);
215     	}
216     	
217     	/**
218     	* Returns: array of needed drop methods by this method
219     	*/
220     	string[] needDrop(string method)
221     	{
222     		auto p = method in dropMap;
223     		
224     		if (p is null) return null;
225     		
226     		return cast(string[]) *p;
227     	}
228     	
229     	/**
230     	* Returns: set_username in json_rpc
231     	*/
232     	bool needAuth(string method)
233     	{
234     		shared Entry* p;
235     		
236     		p = method in map;
237     		
238     		if (p)
239     		{
240     			auto entry = cast(Entry) *p;
241     			
242     			return entry.set_username;
243     		}
244     		
245     		return false;
246     	}
247     	
248     	/**
249     	* Returns: true if method found, and put entry
250     	*/
251     	bool methodFound(string method, out Entry entry)
252     	{	
253     		shared Entry* p;
254     		
255     		p = method in map;
256     		
257     		if (p)
258     		{
259     			entry = cast(Entry) *p;
260     			
261     			return true;
262     		}
263     		
264     		return false;
265     	}
266     	
267     	/**
268     	* Returns entry by method
269     	*/
270     	Entry getEntry(string method)
271     	{
272     		auto p = method in map;
273     		
274     		if (p is null) return Entry();
275     		
276     		return cast(Entry) *p;
277     	}
278     	
279     	/**
280     	* Returns: true if method found
281     	*/
282     	bool methodFound(string method)
283     	{	
284     		return (method in map) !is null;
285     	}
286     	
287     	/// Makes drop map
288     	void makeDropMap()
289     	{
290     		foreach(val; map.byValue())
291     		{
292     			shared string[] arr = new shared string[0];
293     			
294     			if (val.need_cache)
295     			{
296     				if (!val.read_only)
297     				{
298     					foreach(str1; val.reset_caches)
299     					{
300     						foreach(key; map.byKey())
301     						{
302     							foreach(str2; map[key].reset_by)
303     							{
304     								if (str1 == str2) 
305     								{
306     									arr ~= key; //key is method
307     									break;
308     								}
309     							} 
310     						}
311     					}
312     				}
313     			}
314     			
315     			dropMap[val.method] = arr.dup;
316     		}
317     		
318     		// TODO: Check if it regression in 2.066-b1
319     		(cast(shared(string[])[string])dropMap).rehash();
320     	}
321 	}
322     
323 	void toString(scope void delegate(const(char)[]) sink) const
324 	{
325 	    sink("SqlJsonTable(\n");
326 	    foreach(entry; map)
327 	    {
328 	        sink(entry.to!string);
329 	    }
330 	    sink(")\n");
331 	}
332 	
333 	private
334 	{	
335     	alias string[] dropArr;
336     	Entry[string] map;
337     	dropArr[string] dropMap;
338 	}
339 }
340 
341 
342 
343 
344 version(unittest)
345 {
346 	shared SqlJsonTable table;
347 	
348 	void initTable()
349 	{
350 		table = new shared SqlJsonTable();
351 		
352 		auto entry1 = Entry();
353 		entry1.method = "subtract";
354 		entry1.arg_nums = [2];
355 		entry1.need_cache = true;
356 		entry1.reset_caches = ["drop", "safe", "pure"];
357 		entry1.reset_by = ["drop", "unsafe"];
358 		
359 		auto entry2 = Entry();
360 		entry2.method = "multiply";
361 		entry2.arg_nums = [2];
362 		
363 		auto entry3 = Entry();
364 		entry3.method = "divide";
365 		entry3.arg_nums = [2];
366 		entry3.need_cache = true;
367 		entry3.reset_caches = ["trusted", "infinity"];
368 		entry3.reset_by = ["drop", "unsafe"];
369 		entry3.set_username = true;
370 		
371 		table.add(entry1);
372 		table.add(entry2);
373 		table.add(entry3);
374 	}
375 }
376 
377 unittest
378 {
379 	scope(failure)
380 	{
381 		assert(false, "sql_json unittest failed");
382 	}
383 	
384 	initTable();
385 }
386