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 }