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