1 // Written in D programming language 2 /** 3 * This module defines high-level wrapper around libpq bindings. 4 * 5 * The major goals: 6 * <ul> 7 * <li>Get more control over library errors (by converting to exceptions)</li> 8 * <li>Create layer that can be mocked in purpose of unittesting</li> 9 * </ul> 10 * 11 * Copyright: © 2014 DSoftOut 12 * License: Subject to the terms of the MIT license, as written in the included LICENSE file. 13 * Authors: NCrashed <ncrashed@gmail.com> 14 */ 15 module pgator.db.pq.api; 16 17 import derelict.pq.pq; 18 public import pgator.db.pq.types.oids; 19 import pgator.db.connection; 20 import pgator.db.pq.types.conv; 21 import vibe.data.bson; 22 import dlogg.log; 23 24 /** 25 * All exceptions thrown by postgres api is inherited from this exception. 26 */ 27 class PGException : Exception 28 { 29 @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__) 30 { 31 super(msg, file, line); 32 } 33 } 34 35 /** 36 * The exception is thrown when libpq ran in out of memory problems. 37 */ 38 class PGMemoryLackException : PGException 39 { 40 @safe pure nothrow this(string file = __FILE__, size_t line = __LINE__) 41 { 42 super("PostgreSQL API: not enough memory!", file, line); 43 } 44 } 45 46 /** 47 * The exception is thrown when PGconn has a problem with reconnecting process. 48 */ 49 class PGReconnectException : PGException 50 { 51 @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__) 52 { 53 super(msg, file, line); 54 } 55 } 56 57 /** 58 * The exception is thrown when postgres ran in problem with query processing. 59 */ 60 class PGQueryException : PGException 61 { 62 @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__) 63 { 64 super(msg, file, line); 65 } 66 } 67 68 /** 69 * The exception is thrown when postgres ran in problem with parameters escaping. 70 */ 71 class PGEscapeException : PGException 72 { 73 @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__) 74 { 75 super(msg, file, line); 76 } 77 } 78 79 /** 80 * The exception is thrown when postgres ran in problem with query processing. 81 */ 82 class PGParamNotExistException : PGException 83 { 84 private string mParam; 85 86 @safe pure nothrow this(string param, string file = __FILE__, size_t line = __LINE__) 87 { 88 mParam = param; 89 super("Connection parameter '"~param~"' doesn't exist!", file, line); 90 } 91 92 /** 93 * Parameter that raised the exception 94 */ 95 string param() @property 96 { 97 return mParam; 98 } 99 } 100 101 /** 102 * Prototype: PGResult 103 */ 104 interface IPGresult 105 { 106 synchronized: 107 108 /** 109 * Prototype: PQresultStatus 110 */ 111 ExecStatusType resultStatus() nothrow const; 112 113 /** 114 * Prototype: PQresStatus 115 * Note: same as resultStatus, but converts 116 * the enum to human-readable string. 117 */ 118 string resStatus() const; 119 120 /** 121 * Prototype: PQresultErrorMessage 122 */ 123 string resultErrorMessage() const; 124 125 /** 126 * Prototype: PQclear 127 */ 128 void clear() nothrow; 129 130 /** 131 * Prototype: PQntuples 132 */ 133 size_t ntuples() const nothrow; 134 135 /** 136 * Prototype: PQnfields 137 */ 138 size_t nfields() const nothrow; 139 140 /** 141 * Prototype: PQfname 142 */ 143 string fname(size_t colNumber) const; 144 145 /** 146 * Prototype: PQfformat 147 */ 148 bool isBinary(size_t colNumber) const; 149 150 /** 151 * Prototype: PQgetvalue 152 */ 153 string asString(size_t rowNumber, size_t colNumber) const; 154 155 /** 156 * Prototype: PQgetvalue 157 */ 158 ubyte[] asBytes(size_t rowNumber, size_t colNumber) const; 159 160 /** 161 * Prototype: PQgetisnull 162 */ 163 bool getisnull(size_t rowNumber, size_t colNumber) const; 164 165 /** 166 * Prototype: PQgetlength 167 */ 168 size_t getLength(size_t rowNumber, size_t colNumber) const; 169 170 /** 171 * Prototype: PQftype 172 */ 173 PQType ftype(size_t colNumber) const; 174 175 /** 176 * Creates Bson from result in column echelon order. 177 * 178 * Bson consists of named arrays of column values. 179 */ 180 final Bson asColumnBson(shared IConnection conn) 181 { 182 Bson[string] fields; 183 foreach(i; 0..nfields) 184 { 185 Bson[] rows; 186 foreach(j; 0..ntuples) 187 { 188 if(getisnull(j, i)) 189 { 190 rows ~= Bson(null); 191 } 192 else 193 { 194 rows ~= pqToBson(ftype(i), asBytes(j, i), conn, logger); 195 } 196 } 197 fields[fname(i)] = Bson(rows); 198 } 199 200 return Bson(fields); 201 } 202 203 /** 204 * Creates Bson from result in row echelon order. 205 * 206 * Each row in result is represented as structure with column fields. 207 * 208 * Authors: Zaramzan <shamyan.roman@gmail.com> 209 */ 210 final Bson asRowBson(shared IConnection conn) 211 { 212 Bson[] arr = new Bson[0]; 213 214 foreach(i; 0..ntuples) 215 { 216 Bson[string] entry; 217 218 foreach(j; 0..nfields) 219 { 220 if(getisnull(i, j)) 221 { 222 entry[fname(j)] = Bson(null); 223 } 224 else 225 { 226 entry[fname(j)] = pqToBson(ftype(j), asBytes(i, j), conn, logger); 227 } 228 } 229 230 arr ~= Bson(entry); 231 } 232 233 return Bson(arr); 234 235 } 236 237 /// Getting local logger 238 protected shared(ILogger) logger() nothrow; 239 } 240 241 /** 242 * Prototype: PGconn 243 */ 244 interface IPGconn 245 { 246 synchronized: 247 248 /** 249 * Prototype: PQconnectPoll 250 */ 251 PostgresPollingStatusType poll() nothrow; 252 253 /** 254 * Prototype: PQstatus 255 */ 256 ConnStatusType status() nothrow; 257 258 /** 259 * Prototype: PQfinish 260 * Note: this function should be called even 261 * there was an error. 262 */ 263 void finish() nothrow; 264 265 /** 266 * Prototype: PQflush 267 */ 268 bool flush() nothrow const; 269 270 /** 271 * Prototype: PQresetStart 272 * Throws: PGReconnectException 273 */ 274 void resetStart(); 275 276 /** 277 * Prototype: PQresetPoll 278 */ 279 PostgresPollingStatusType resetPoll() nothrow; 280 281 /** 282 * Prototype: PQhost 283 */ 284 string host() const nothrow @property; 285 286 /** 287 * Prototype: PQdb 288 */ 289 string db() const nothrow @property; 290 291 /** 292 * Prototype: PQuser 293 */ 294 string user() const nothrow @property; 295 296 /** 297 * Prototype: PQport 298 */ 299 string port() const nothrow @property; 300 301 /** 302 * Prototype: PQerrorMessage 303 */ 304 string errorMessage() const nothrow @property; 305 306 /** 307 * Prototype: PQsendQueryParams 308 * Note: This is simplified version of the command that 309 * handles only string params. 310 * Warning: libpq doesn't support multiple SQL commands in 311 * the function. See the sendQueryParamsExt as 312 * an extended version of the function. 313 * Throws: PGQueryException 314 */ 315 void sendQueryParams(string command, string[] paramValues); 316 317 /** 318 * Prototype: PQsendQuery 319 * Throws: PGQueryException 320 */ 321 void sendQuery(string command); 322 323 /** 324 * Like sendQueryParams but uses libpq escaping functions 325 * and sendQuery. 326 * 327 * The main advantage of the function is ability to handle 328 * multiple SQL commands in one query. 329 * Throws: PGQueryException 330 */ 331 void sendQueryParamsExt(string command, string[] paramValues); 332 333 /** 334 * Prototype: PQgetResult 335 * Note: Even when PQresultStatus indicates a fatal error, 336 * PQgetResult should be called until it returns a null pointer 337 * to allow libpq to process the error information completely. 338 * Note: A null pointer is returned when the command is complete and t 339 * here will be no more results. 340 */ 341 shared(IPGresult) getResult() nothrow; 342 343 /** 344 * Prototype: PQconsumeInput 345 * Throws: PGQueryException 346 */ 347 void consumeInput(); 348 349 /** 350 * Prototype: PQisBusy 351 */ 352 bool isBusy() nothrow; 353 354 /** 355 * Prototype: PQescapeLiteral 356 * Throws: PGEscapeException 357 */ 358 string escapeLiteral(string msg) const; 359 360 /** 361 * Prototype: PQparameterStatus 362 * Throws: PGParamNotExistException 363 */ 364 string parameterStatus(string param) const; 365 366 /** 367 * Prototype: PQsetNoticeReceiver 368 */ 369 PQnoticeReceiver setNoticeReceiver(PQnoticeReceiver proc, void* arg) nothrow; 370 371 /** 372 * Prototype: PQsetNoticeProcessor 373 */ 374 PQnoticeProcessor setNoticeProcessor(PQnoticeProcessor proc, void* arg) nothrow; 375 376 /// Getting local logger 377 protected shared(ILogger) logger() nothrow; 378 } 379 380 /** 381 * OOP styled libpq wrapper to automatically handle library loading/unloading and 382 * to provide mockable layer for unittests. 383 */ 384 shared interface IPostgreSQL 385 { 386 /** 387 * Prototype: PQconnectStart 388 * Throws: PGMemoryLackException 389 */ 390 shared(IPGconn) startConnect(string conninfo); 391 392 /** 393 * Prototype: PQping 394 */ 395 PGPing ping(string conninfo) nothrow; 396 397 /** 398 * Should be called to free libpq resources. The method 399 * unloads library from application memory. 400 */ 401 void finalize() nothrow; 402 403 protected 404 { 405 /** 406 * Should be called in class constructor. The method 407 * loads library in memory. 408 */ 409 void initialize(); 410 411 /// Getting local logger 412 shared(ILogger) logger() nothrow; 413 } 414 }