1 // Written in D programming language 2 /** 3 * This module defines realization of high-level libpq api. 4 * 5 * See_Also: pgator.db.pq.api 6 * 7 * Copyright: © 2014 DSoftOut 8 * License: Subject to the terms of the MIT license, as written in the included LICENSE file. 9 * Authors: NCrashed <ncrashed@gmail.com> 10 */ 11 module pgator.db.pq.libpq; 12 13 public import pgator.db.pq.api; 14 public import derelict.pq.pq; 15 import derelict.util.exception; 16 import dlogg.log; 17 import std.exception; 18 import std..string; 19 import std.regex; 20 import std.conv; 21 import core.memory; 22 import core.exception: RangeError; 23 24 //import util; 25 26 synchronized class CPGresult : IPGresult 27 { 28 this(PGresult* result, shared ILogger plogger) nothrow 29 { 30 this.mResult = cast(shared)result; 31 this.mLogger = plogger; 32 } 33 34 private shared PGresult* mResult; 35 36 private PGresult* result() nothrow const 37 { 38 return cast(PGresult*)mResult; 39 } 40 41 private shared(ILogger) mLogger; 42 43 protected shared(ILogger) logger() 44 { 45 return mLogger; 46 } 47 48 /** 49 * Prototype: PQresultStatus 50 */ 51 ExecStatusType resultStatus() nothrow const 52 in 53 { 54 assert(result !is null, "PGconn was finished!"); 55 assert(PQresultStatus !is null, "DerelictPQ isn't loaded!"); 56 } 57 body 58 { 59 return PQresultStatus(result); 60 } 61 62 /** 63 * Prototype: PQresStatus 64 * Note: same as resultStatus, but converts 65 * the enum to human-readable string. 66 */ 67 string resStatus() const 68 in 69 { 70 assert(result !is null, "PGconn was finished!"); 71 assert(PQresultStatus !is null, "DerelictPQ isn't loaded!"); 72 assert(PQresStatus !is null, "DerelictPQ isn't loaded!"); 73 } 74 body 75 { 76 return fromStringz(PQresStatus(PQresultStatus(result))).idup; 77 } 78 79 /** 80 * Prototype: PQresultErrorMessage 81 */ 82 string resultErrorMessage() const 83 in 84 { 85 assert(result !is null, "PGconn was finished!"); 86 assert(PQresultErrorMessage !is null, "DerelictPQ isn't loaded!"); 87 } 88 body 89 { 90 return fromStringz(PQresultErrorMessage(result)).idup; 91 } 92 93 /** 94 * Prototype: PQclear 95 */ 96 void clear() nothrow 97 in 98 { 99 assert(result !is null, "PGconn was finished!"); 100 assert(PQclear !is null, "DerelictPQ isn't loaded!"); 101 } 102 body 103 { 104 PQclear(result); 105 mResult = null; 106 } 107 108 /** 109 * Prototype: PQntuples 110 */ 111 size_t ntuples() nothrow const 112 in 113 { 114 assert(result !is null, "PGconn was finished!"); 115 assert(PQntuples !is null, "DerelictPQ isn't loaded!"); 116 } 117 body 118 { 119 return cast(size_t)PQntuples(result); 120 } 121 122 /** 123 * Prototype: PQnfields 124 */ 125 size_t nfields() nothrow const 126 in 127 { 128 assert(result !is null, "PGconn was finished!"); 129 assert(PQnfields !is null, "DerelictPQ isn't loaded!"); 130 } 131 body 132 { 133 return cast(size_t)PQnfields(result); 134 } 135 136 /** 137 * Prototype: PQfname 138 */ 139 string fname(size_t colNumber) const 140 in 141 { 142 assert(result !is null, "PGconn was finished!"); 143 assert(PQfname !is null, "DerelictPQ isn't loaded!"); 144 } 145 body 146 { 147 return enforceEx!Error(fromStringz(PQfname(result, cast(uint)colNumber)).idup); 148 } 149 150 /** 151 * Prototype: PQfformat 152 */ 153 bool isBinary(size_t colNumber) const 154 in 155 { 156 assert(result !is null, "PGconn was finished!"); 157 assert(PQfformat !is null, "DerelictPQ isn't loaded!"); 158 } 159 body 160 { 161 return PQfformat(result, cast(uint)colNumber) == 1; 162 } 163 164 /** 165 * Prototype: PQgetvalue 166 */ 167 string asString(size_t rowNumber, size_t colNumber) const 168 in 169 { 170 assert(result !is null, "PGconn was finished!"); 171 assert(PQgetvalue !is null, "DerelictPQ isn't loaded!"); 172 } 173 body 174 { 175 import std.stdio; writeln(getLength(rowNumber, colNumber)); 176 return fromStringz(cast(immutable(char)*)PQgetvalue(result, cast(uint)rowNumber, cast(uint)colNumber)); 177 } 178 179 /** 180 * Prototype: PQgetvalue 181 */ 182 ubyte[] asBytes(size_t rowNumber, size_t colNumber) const 183 in 184 { 185 assert(result !is null, "PGconn was finished!"); 186 assert(PQgetvalue !is null, "DerelictPQ isn't loaded!"); 187 } 188 body 189 { 190 auto l = getLength(rowNumber, colNumber); 191 auto res = new ubyte[l]; 192 auto bytes = PQgetvalue(result, cast(uint)rowNumber, cast(uint)colNumber); 193 foreach(i; 0..l) 194 res[i] = bytes[i]; 195 return res; 196 } 197 198 /** 199 * Prototype: PQgetisnull 200 */ 201 bool getisnull(size_t rowNumber, size_t colNumber) const 202 in 203 { 204 assert(result !is null, "PGconn was finished!"); 205 assert(PQgetisnull !is null, "DerelictPQ isn't loaded!"); 206 } 207 body 208 { 209 return PQgetisnull(result, cast(uint)rowNumber, cast(uint)colNumber) != 0; 210 } 211 212 /** 213 * Prototype: PQgetlength 214 */ 215 size_t getLength(size_t rowNumber, size_t colNumber) const 216 in 217 { 218 assert(result !is null, "PGconn was finished!"); 219 assert(PQgetisnull !is null, "DerelictPQ isn't loaded!"); 220 } 221 body 222 { 223 return cast(size_t)PQgetlength(result, cast(uint)rowNumber, cast(uint)colNumber); 224 } 225 226 /** 227 * Prototype: PQftype 228 */ 229 PQType ftype(size_t colNumber) const 230 in 231 { 232 assert(result !is null, "PGconn was finished!"); 233 assert(PQftype !is null, "DerelictPQ isn't loaded!"); 234 } 235 body 236 { 237 return cast(PQType)PQftype(result, cast(uint)colNumber); 238 } 239 } 240 241 synchronized class CPGconn : IPGconn 242 { 243 this(PGconn* conn, shared ILogger plogger) nothrow 244 { 245 this.mConn = cast(shared)conn; 246 this.mLogger = plogger; 247 } 248 249 private shared PGconn* mConn; 250 251 private PGconn* conn() const nothrow 252 { 253 return cast(PGconn*)mConn; 254 } 255 256 private shared(ILogger) mLogger; 257 258 protected shared(ILogger) logger() nothrow 259 { 260 return mLogger; 261 } 262 263 /** 264 * Prototype: PQconnectPoll 265 */ 266 PostgresPollingStatusType poll() nothrow 267 in 268 { 269 assert(conn !is null, "PGconn was finished!"); 270 assert(PQconnectPoll !is null, "DerelictPQ isn't loaded!"); 271 } 272 body 273 { 274 return PQconnectPoll(conn); 275 } 276 277 /** 278 * Prototype: PQstatus 279 */ 280 ConnStatusType status() nothrow 281 in 282 { 283 assert(conn !is null, "PGconn was finished!"); 284 assert(PQstatus !is null, "DerelictPQ isn't loaded!"); 285 } 286 body 287 { 288 return PQstatus(conn); 289 } 290 291 /** 292 * Prototype: PQfinish 293 * Note: this function should be called even 294 * there was an error. 295 */ 296 void finish() nothrow 297 in 298 { 299 assert(conn !is null, "PGconn was finished!"); 300 assert(PQfinish !is null, "DerelictPQ isn't loaded!"); 301 } 302 body 303 { 304 scope(failure) {} 305 306 PQfinish(conn); 307 mConn = null; 308 } 309 310 /** 311 * Prototype: PQflush 312 */ 313 bool flush() nothrow const 314 in 315 { 316 assert(conn !is null, "PGconn was finished!"); 317 assert(PQfinish !is null, "DerelictPQ isn't loaded!"); 318 } 319 body 320 { 321 return PQflush(conn) != 0; 322 } 323 324 /** 325 * Prototype: PQresetStart 326 * Throws: PGReconnectException 327 */ 328 void resetStart() 329 in 330 { 331 assert(conn !is null, "PGconn was finished!"); 332 assert(PQresetStart !is null, "DerelictPQ isn't loaded!"); 333 } 334 body 335 { 336 auto res = PQresetStart(conn); 337 if(res == 1) 338 throw new PGReconnectException(errorMessage); 339 } 340 341 /** 342 * Prototype: PQresetPoll 343 */ 344 PostgresPollingStatusType resetPoll() nothrow 345 in 346 { 347 assert(conn !is null, "PGconn was finished!"); 348 assert(PQresetPoll !is null, "DerelictPQ isn't loaded!"); 349 } 350 body 351 { 352 return PQresetPoll(conn); 353 } 354 355 /** 356 * Prototype: PQhost 357 */ 358 string host() const nothrow @property 359 in 360 { 361 assert(conn !is null, "PGconn was finished!"); 362 assert(PQhost !is null, "DerelictPQ isn't loaded!"); 363 } 364 body 365 { 366 scope(failure) return ""; 367 return fromStringz(PQhost(conn)).idup; 368 } 369 370 /** 371 * Prototype: PQdb 372 */ 373 string db() const nothrow @property 374 in 375 { 376 assert(conn !is null, "PGconn was finished!"); 377 assert(PQdb !is null, "DerelictPQ isn't loaded!"); 378 } 379 body 380 { 381 scope(failure) return ""; 382 return fromStringz(PQdb(conn)).idup; 383 } 384 385 /** 386 * Prototype: PQuser 387 */ 388 string user() const nothrow @property 389 in 390 { 391 assert(conn !is null, "PGconn was finished!"); 392 assert(PQuser !is null, "DerelictPQ isn't loaded!"); 393 } 394 body 395 { 396 scope(failure) return ""; 397 return fromStringz(PQuser(conn)).idup; 398 } 399 400 /** 401 * Prototype: PQport 402 */ 403 string port() const nothrow @property 404 in 405 { 406 assert(conn !is null, "PGconn was finished!"); 407 assert(PQport !is null, "DerelictPQ isn't loaded!"); 408 } 409 body 410 { 411 scope(failure) return ""; 412 return fromStringz(PQport(conn)).idup; 413 } 414 415 /** 416 * Prototype: PQerrorMessage 417 */ 418 string errorMessage() const nothrow @property 419 in 420 { 421 assert(conn !is null, "PGconn was finished!"); 422 assert(PQerrorMessage !is null, "DerelictPQ isn't loaded!"); 423 } 424 body 425 { 426 scope(failure) return ""; 427 return fromStringz(PQerrorMessage(conn)).idup; 428 } 429 430 /** 431 * Prototype: PQsendQueryParams 432 * Note: This is simplified version of the command that 433 * handles only string params. 434 * Throws: PGQueryException 435 */ 436 void sendQueryParams(string command, string[] paramValues) 437 in 438 { 439 assert(conn !is null, "PGconn was finished!"); 440 assert(PQsendQueryParams !is null, "DerelictPQ isn't loaded!"); 441 } 442 body 443 { 444 const (ubyte)** toPlainArray(string[] arr) 445 { 446 auto ptrs = new char*[arr.length]; 447 foreach(i, ref p; ptrs) 448 { 449 // special case to handle SQL null values 450 if(arr[i] is null) 451 { 452 p = null; 453 } 454 else 455 { 456 p = cast(char*) arr[i].toStringz; 457 } 458 } 459 return cast(const(ubyte)**)ptrs.ptr; 460 } 461 462 const(int)* genFormatArray(string[] params) 463 { 464 auto formats = new int[params.length]; 465 foreach(i, p; params) 466 { 467 if(p is null) 468 { 469 formats[i] = 1; 470 } 471 } 472 return formats.ptr; 473 } 474 475 // type error in bindings int -> size_t, const(char)* -> const(char*), const(ubyte)** -> const(ubyte**) 476 auto res = PQsendQueryParams(conn, command.toStringz, cast(int)paramValues.length, null 477 , toPlainArray(paramValues), null, null, 1); 478 if (res == 0) 479 { 480 throw new PGQueryException(errorMessage); 481 } 482 } 483 484 /** 485 * Prototype: PQsendQuery 486 * Throws: PGQueryException 487 */ 488 void sendQuery(string command) 489 in 490 { 491 assert(conn !is null, "PGconn was finished!"); 492 assert(PQsendQuery !is null, "DerelictPQ isn't loaded!"); 493 } 494 body 495 { 496 auto res = PQsendQuery(conn, command.toStringz); 497 if (res == 0) 498 { 499 throw new PGQueryException(errorMessage); 500 } 501 } 502 503 /** 504 * Like sendQueryParams but uses libpq escaping functions 505 * and sendQuery. 506 * 507 * The main advantage of the function is ability to handle 508 * multiple SQL commands in one query. 509 * Throws: PGQueryException 510 */ 511 void sendQueryParamsExt(string command, string[] paramValues) 512 { 513 try 514 { 515 sendQuery(escapeParams(command, paramValues)); 516 } 517 catch(PGEscapeException e) 518 { 519 throw new PGQueryException(e.msg); 520 } 521 } 522 523 /** 524 * Prototype: PQgetResult 525 * Note: Even when PQresultStatus indicates a fatal error, 526 * PQgetResult should be called until it returns a null pointer 527 * to allow libpq to process the error information completely. 528 * Note: A null pointer is returned when the command is complete and t 529 * here will be no more results. 530 */ 531 shared(IPGresult) getResult() nothrow 532 in 533 { 534 assert(conn !is null, "PGconn was finished!"); 535 assert(PQgetResult !is null, "DerelictPQ isn't loaded!"); 536 } 537 body 538 { 539 auto res = PQgetResult(conn); 540 if(res is null) return null; 541 return new shared CPGresult(res, logger); 542 } 543 544 /** 545 * Prototype: PQconsumeInput 546 * Throws: PGQueryException 547 */ 548 void consumeInput() 549 in 550 { 551 assert(conn !is null, "PGconn was finished!"); 552 assert(PQconsumeInput !is null, "DerelictPQ isn't loaded!"); 553 } 554 body 555 { 556 auto res = PQconsumeInput(conn); 557 if(res == 0) 558 throw new PGQueryException(errorMessage); 559 } 560 561 /** 562 * Prototype: PQisBusy 563 */ 564 bool isBusy() nothrow 565 in 566 { 567 assert(conn !is null, "PGconn was finished!"); 568 assert(PQisBusy !is null, "DerelictPQ isn't loaded!"); 569 } 570 body 571 { 572 return PQisBusy(conn) > 0; 573 } 574 575 /** 576 * Prototype: PQescapeLiteral 577 * Throws: PGEscapeException 578 */ 579 string escapeLiteral(string msg) const 580 in 581 { 582 assert(conn !is null, "PGconn was finished!"); 583 assert(PQescapeLiteral !is null, "DerelictPQ isn't loaded!"); 584 } 585 body 586 { 587 auto res = PQescapeLiteral(conn, msg.toStringz, msg.length); 588 if(res is null) throw new PGEscapeException(errorMessage); 589 return fromStringz(res).idup; 590 } 591 592 /** 593 * Escaping query like PQexecParams does. This function 594 * enables use of multiple SQL commands in one query. 595 */ 596 private string escapeParams(string query, string[] args) 597 { 598 foreach(i, arg; args) 599 { 600 auto reg = regex(text(`\$`, i)); 601 query = query.replaceAll(reg, escapeLiteral(arg)); 602 } 603 return query; 604 } 605 606 /** 607 * Prototype: PQparameterStatus 608 * Throws: PGParamNotExistException 609 */ 610 string parameterStatus(string param) const 611 in 612 { 613 assert(conn !is null, "PGconn was finished!"); 614 assert(PQparameterStatus !is null, "DerelictPQ isn't loaded!"); 615 } 616 body 617 { 618 // fix bindings char* -> const char* 619 auto res = PQparameterStatus(conn, cast(char*)toStringz(param)); 620 if(res is null) 621 throw new PGParamNotExistException(param); 622 623 return res.fromStringz.idup; 624 } 625 626 /** 627 * Prototype: PQsetNoticeReceiver 628 */ 629 PQnoticeReceiver setNoticeReceiver(PQnoticeReceiver proc, void* arg) nothrow 630 in 631 { 632 assert(conn !is null, "PGconn was finished!"); 633 assert(PQsetNoticeReceiver !is null, "DerelictPQ isn't loaded!"); 634 } 635 body 636 { 637 return PQsetNoticeReceiver(conn, proc, arg); 638 } 639 640 /** 641 * Prototype: PQsetNoticeProcessor 642 */ 643 PQnoticeProcessor setNoticeProcessor(PQnoticeProcessor proc, void* arg) nothrow 644 in 645 { 646 assert(conn !is null, "PGconn was finished!"); 647 assert(PQsetNoticeProcessor !is null, "DerelictPQ isn't loaded!"); 648 } 649 body 650 { 651 return PQsetNoticeProcessor(conn, proc, arg); 652 } 653 } 654 655 synchronized class PostgreSQL : IPostgreSQL 656 { 657 this(shared ILogger plogger) 658 { 659 this.mLogger = plogger; 660 initialize(); 661 } 662 663 private shared(ILogger) mLogger; 664 665 protected shared(ILogger) logger() 666 { 667 return mLogger; 668 } 669 670 /** 671 * Should be called to free libpq resources. The method 672 * unloads library from application memory. 673 */ 674 void finalize() nothrow 675 { 676 try 677 { 678 GC.collect(); 679 DerelictPQ.unload(); 680 } catch(Throwable th) 681 { 682 683 } 684 } 685 686 /** 687 * Prototype: PQconnectStart 688 * Throws: PGMemoryLackException 689 */ 690 shared(IPGconn) startConnect(string conninfo) 691 in 692 { 693 assert(PQconnectStart !is null, "DerelictPQ isn't loaded!"); 694 } 695 body 696 { 697 auto conn = enforceEx!PGMemoryLackException(PQconnectStart(cast(char*)conninfo.toStringz)); 698 return new shared CPGconn(conn, logger); 699 } 700 701 /** 702 * Prototype: PQping 703 */ 704 PGPing ping(string conninfo) nothrow 705 in 706 { 707 assert(PQping !is null, "DerelictPQ isn't loaded!"); 708 } 709 body 710 { 711 return PQping(cast(char*)conninfo.toStringz); 712 } 713 714 protected 715 { 716 /** 717 * Should be called in class constructor. The method 718 * loads library in memory. 719 */ 720 void initialize() 721 { 722 try 723 { 724 version(linux) 725 { 726 try 727 { 728 DerelictPQ.load(); 729 } catch(DerelictException e) 730 { 731 // try with some frequently names 732 DerelictPQ.load("libpq.so.0,libpq.so.5"); 733 } 734 } 735 else 736 { 737 DerelictPQ.load(); 738 } 739 } catch(SymbolLoadException e) 740 { 741 if( e.symbolName != "PQconninfo" && 742 e.symbolName != "PQsetSingleRowMode") 743 { 744 throw e; 745 } 746 } 747 } 748 } 749 }