1 // Written in D programming language
2 /**
3 *   Medium sized wrapper around PostgreSQL connection. 
4 *   
5 *   Copyright: © 2014 DSoftOut
6 *   License: Subject to the terms of the MIT license, as written in the included LICENSE file.
7 *   Authors: NCrashed <ncrashed@gmail.com>
8 */
9 module pgator.db.connection;
10 
11 import pgator.db.pq.api;
12 import std.container;
13 import std.datetime;
14 import std.range;
15 
16 /**
17 *    The exception is thrown when connection attempt to SQL server is failed due some reason.
18 */
19 class ConnectException : Exception
20 {
21     string server;
22     
23     @safe pure nothrow this(string server, string msg, string file = __FILE__, size_t line = __LINE__)
24     {
25         this.server = server;
26         super("Failed to connect to SQL server "~server~", reason: " ~ msg, file, line); 
27     }
28 }
29 
30 /**
31 *   The exception is thrown when $(B reconnect) method is called, but there wasn't any call of
32 *   $(B connect) method to grab connection string from.
33 */
34 class ReconnectException : ConnectException
35 {
36     @safe pure nothrow this(string server, string file = __FILE__, size_t line = __LINE__)
37     {
38         super(server, "Connection reconnect method is called, but there wasn't any call of "
39                       "connect method to grab connection string from", file, line);
40     }
41 }
42 
43 /**
44 *   The exception is thrown when query is failed due some reason.
45 */
46 class QueryException : Exception
47 {
48     @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__)
49     {
50         super("Query to SQL server is failed, reason: " ~ msg, file, line); 
51     }
52 }
53 
54 /**
55 *   Describes result of connection status polling.
56 */
57 enum ConnectionStatus
58 {
59     /// Connection is in progress
60     Pending,
61     /// Connection is finished with error
62     Error,
63     /// Connection is finished successfully
64     Finished
65 }
66 
67 /**
68 *   Describes result of quering status polling.
69 */
70 enum QueringStatus
71 {
72     /// Quering is in progress
73     Pending,
74     /// SQL server returned an error
75     Error,
76     /// SQL server returned normal result
77     Finished
78 }
79 
80 /**
81 *   Representing server configuration for
82 *   displaying dates and converting ambitious values.
83 */
84 struct DateFormat
85 {
86     /**
87     *   Representing output format.
88     */
89     enum StringFormat
90     {
91         ISO,
92         Postgres,
93         SQL,
94         German,
95         Unknown
96     }
97     
98     static StringFormat stringFormatIn(string val)
99     {
100         foreach(s; __traits(allMembers, StringFormat))
101         {
102             if(val == s) return mixin("StringFormat."~s);
103         }
104         return StringFormat.Unknown;
105     }
106     
107     /**
108     *   Representing behavior for ambitious values.
109     */
110     enum OrderFormat
111     {
112         /// Day Month Year
113         DMY, 
114         /// Month Day Year
115         MDY,
116         /// Year Month Day
117         YMD,
118         /// Unsupported by the bindings
119         Unknown
120     }
121     
122     static OrderFormat orderFormatIn(string val)
123     {
124         foreach(s; __traits(allMembers, OrderFormat))
125         {
126             if(val == s) return mixin("OrderFormat."~s);
127         }
128         return OrderFormat.Unknown;
129     }
130     
131     /// Current output format
132     StringFormat stringFormat;
133     /// Current order format
134     OrderFormat  orderFormat;
135     
136     this(string stringFmt, string orderFmt)
137     {
138         stringFormat = stringFormatIn(stringFmt);
139         orderFormat = orderFormatIn(orderFmt);
140     }
141 }
142 
143 /**
144 *   Enum describes two possible server configuration for timestamp format.
145 */
146 enum TimestampFormat
147 {
148     /// Server uses long (usecs) to encode time 
149     Int64,
150     /// Server uses double (seconds) to encode time
151     Float8
152 }
153 
154 /**
155 *    Handles a single connection to a SQL server.
156 */
157 interface IConnection
158 {
159     synchronized:
160     
161     /**
162     *    Tries to establish connection with a SQL server described
163     *    in $(B connString). 
164     *
165     *    Throws: ConnectException
166     */
167     void connect(string connString);
168     
169     /**
170     *   Tries to establish connection with a SQL server described
171     *   in previous call of $(B connect). 
172     *
173     *   Should throw ReconnectException if method cannot get stored
174     *   connection string (the $(B connect) method wasn't called).
175     *
176     *   Throws: ConnectException, ReconnectException
177     */
178     void reconnect();
179     
180     /**
181     *   Returns current status of connection.
182     */
183     ConnectionStatus pollConnectionStatus() nothrow;
184     
185     /**
186     *   If connection process is ended with error state, then
187     *   throws ConnectException, else do nothing.
188     *
189     *   Throws: ConnectException
190     */    
191     void pollConnectionException();
192     
193     /**
194     *   Initializes querying process in non-blocking manner.
195     *   Throws: QueryException
196     */
197     void postQuery(string com, string[] params = []);
198     
199     /**
200     *   Returns quering status of connection.
201     */
202     QueringStatus pollQueringStatus() nothrow;
203     
204     /**
205     *   If quering process is ended with error state, then
206     *   throws QueryException, else do nothing.
207     *
208     *   Throws: QueryException
209     */
210     void pollQueryException();
211     
212     /**
213     *   Returns query result, if $(B pollQueringStatus) shows that
214     *   query is processed without errors, else blocks the caller
215     *   until the answer is arrived.
216     */
217     InputRange!(shared IPGresult) getQueryResult();
218     
219     /**
220     *    Closes connection to the SQL server instantly.    
221     *    
222     *    Also should interrupt connections in progress.
223     *
224     *    Calls $(B callback) when closed.
225     */
226     void disconnect() nothrow;
227     
228     /**
229     *   Returns SQL server name (domain) the connection is desired to connect to.
230     *   If connection isn't ever established (or tried) the method returns empty string.
231     */
232     string server() nothrow const @property;
233     
234     /**
235     *   Returns current date output format and ambitious values converting behavior.
236     *   Throws: QueryException
237     */
238     DateFormat dateFormat() @property;
239     
240     /**
241     *   Returns actual time stamp representation format used in server.
242     *
243     *   Note: This property tells particular HAVE_INT64_TIMESTAMP version flag that is used
244     *         by remote server.
245     *
246     *   Note: Will fallback to Int64 value if server protocol doesn't support acquiring of
247     *         'integer_datetimes' parameter.
248     */
249     TimestampFormat timestampFormat() @property;
250     
251     /**
252     *   Returns server time zone. This value is important to handle 
253     *   time stamps with time zone specified as libpq doesn't send
254     *   the information with time stamp.
255     *
256     *   Note: Will fallback to UTC value if server protocol doesn't support acquiring of
257     *         'TimeZone' parameter or server returns invalid time zone name.
258     */
259     immutable(TimeZone) timeZone() @property;
260     
261     /**
262     *   Sending senseless query to the server to check if the connection is
263     *   actually alive (e.g. nothing can detect fail after postgresql restart but
264     *   query).
265     */
266     bool testAlive() nothrow;
267     
268     /**
269     *   Blocking wrapper to one-command query execution.
270     */
271     final InputRange!(shared IPGresult) execQuery(string com, string[] params = [])
272     {
273         postQuery(com, params);
274         
275         QueringStatus status;
276         do
277         {
278             status = pollQueringStatus;
279             if(status == QueringStatus.Error) pollQueryException;
280         } 
281         while(status != QueringStatus.Finished);
282         
283         return getQueryResult;
284     }
285     
286     /**
287     *   Returns true if the connection stores info/warning/error messages.
288     */
289     bool hasRaisedMsgs();
290     
291     /**
292     *   Returns all saved info/warning/error messages from the connection.
293     */
294     InputRange!string raisedMsgs();
295     
296     /**
297     *   Cleaning inner buffer for info/warning/error messages.
298     */
299     void clearRaisedMsgs();
300 }
301 
302 /**
303 *   Interface that produces connection objects. Used
304 *   to isolate connection pool from particular connection
305 *   realization.
306 */
307 interface IConnectionProvider
308 {
309     /**
310     *   Allocates new connection shared across threads.
311     */
312     synchronized shared(IConnection) allocate();
313 }