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 }