1 module pgator.rpc_table; 2 3 import dpq2.result; 4 import dpq2.oids: OidType; 5 import vibe.core.log; 6 7 struct Method 8 { 9 string name; // TODO: remove it, AA already contains name of method 10 Statement[] statements; 11 bool isMultiStatement = false; 12 bool readOnlyFlag = false; // TODO: rename to isReadOnly 13 bool needAuthVariablesFlag = false; /// pass username and password from HTTP session to SQL session 14 } 15 16 struct Statement 17 { 18 short statementNum = -1; 19 20 // Required parameters: 21 string sqlCommand; 22 string[] argsNames; 23 OidType[] argsOids; 24 string resultName; 25 ResultFormat resultFormat = ResultFormat.TABLE; 26 } 27 28 enum ResultFormat 29 { 30 TABLE, 31 ROTATED, /// rotate result "counterclockwise" 32 ROW, 33 CELL, /// one cell result 34 VOID /// Run without result (only for multi-statement methods) 35 } 36 37 struct ReadMethodsResult 38 { 39 Method[string] methods; 40 size_t loaded; 41 } 42 43 ReadMethodsResult readMethods(immutable Answer answer) 44 { 45 ReadMethodsResult ret; 46 47 foreach(ref r; rangify(answer)) 48 { 49 debug logDebugV("found row: "~r.toString); 50 51 void getOptionalField(T)(string sqlName, ref T result) 52 { 53 try 54 { 55 auto v = r[sqlName]; 56 57 if(v.isNull) 58 throw new Exception("Value of column "~sqlName~" is NULL", __FILE__, __LINE__); 59 60 result = v.as!T; 61 } 62 catch(AnswerException e) 63 { 64 if(e.type != ExceptionType.COLUMN_NOT_FOUND) throw e; 65 } 66 } 67 68 Statement s; 69 Method m; 70 71 // Reading of required parameters 72 try 73 { 74 if(r["method"].isNull) 75 throw new Exception("Method name is NULL", __FILE__, __LINE__); 76 77 m.name = r["method"].as!string; 78 79 if(m.name.length == 0) 80 throw new Exception("Method name is empty", __FILE__, __LINE__); 81 82 if(r["sql_query"].isNull) 83 throw new Exception("sql_query is NULL", __FILE__, __LINE__); 84 85 s.sqlCommand = r["sql_query"].as!string; 86 87 if(r["args"].isNull) 88 { 89 throw new Exception("args[] is NULL", __FILE__, __LINE__); 90 } 91 else 92 { 93 auto arr = r["args"].asArray; 94 95 if(arr.dimsSize.length > 1) 96 throw new Exception("args[] should be one dimensional", __FILE__, __LINE__); 97 98 foreach(ref v; rangify(arr)) 99 { 100 if(v.isNull || v.as!string.length == 0) 101 throw new Exception("args[] contains NULL or empty string", __FILE__, __LINE__); 102 103 s.argsNames ~= v.as!string; 104 } 105 } 106 } 107 catch(Exception e) 108 { 109 logFatal(e.msg~", failed on method "~m.name); 110 break; 111 } 112 113 // Reading of optional parameters 114 try 115 { 116 getOptionalField("read_only", m.readOnlyFlag); 117 getOptionalField("set_auth_variables", m.needAuthVariablesFlag); 118 119 { 120 try 121 { 122 if(!r["statement_num"].isNull) s.statementNum = r["statement_num"].as!short; 123 } 124 catch(AnswerException e) 125 { 126 if(e.type != ExceptionType.COLUMN_NOT_FOUND) throw e; 127 } 128 129 if(s.statementNum >= 0) 130 { 131 m.isMultiStatement = true; 132 getOptionalField("result_name", s.resultName); 133 } 134 } 135 136 { 137 string resultFormatStr = "TABLE"; 138 getOptionalField("result_format", resultFormatStr); 139 140 switch(resultFormatStr) 141 { 142 case "TABLE": 143 s.resultFormat = ResultFormat.TABLE; 144 break; 145 146 case "ROTATED": 147 s.resultFormat = ResultFormat.ROTATED; 148 break; 149 150 case "ROW": 151 s.resultFormat = ResultFormat.ROW; 152 break; 153 154 case "CELL": 155 s.resultFormat = ResultFormat.CELL; 156 break; 157 158 case "VOID": 159 if(s.statementNum >= 0) 160 { 161 s.resultFormat = ResultFormat.VOID; 162 } 163 else 164 { 165 throw new Exception("result_format=VOID only for multi-statement transactions", __FILE__, __LINE__); 166 } 167 break; 168 169 default: 170 throw new Exception("Unknown result format type "~resultFormatStr, __FILE__, __LINE__); 171 } 172 } 173 } 174 catch(Exception e) 175 { 176 logWarn("Skipping "~m.name~": "~e.msg); 177 continue; 178 } 179 180 { 181 auto method = m.name in ret.methods; 182 183 if(method is null) 184 { 185 m.statements ~= s; 186 ret.methods[m.name] = m; 187 } 188 else 189 { 190 if(s.statementNum < 0) 191 { 192 throw new Exception("Duplicate method "~m.name, __FILE__, __LINE__); 193 } 194 else // Insert sorted by statementNum 195 { 196 import std.array: insertInPlace; 197 import std.conv: to; 198 199 foreach(const i; 0 .. method.statements.length) 200 { 201 const storedSNum = method.statements[i].statementNum; 202 203 if(storedSNum == s.statementNum) 204 throw new Exception("Duplicate statement nums "~s.statementNum.to!string~" for method "~m.name, __FILE__, __LINE__); 205 206 if(i == method.statements.length - 1) 207 method.statements ~= s; 208 209 if(storedSNum > s.statementNum) 210 { 211 method.statements.insertInPlace(i, s); 212 break; 213 } 214 } 215 } 216 } 217 } 218 219 ret.loaded++; 220 221 logDebugV("Method "~m.name~" loaded"); 222 } 223 224 return ret; 225 }