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 }