1 /// 2 module dpq.query; 3 4 import dpq.connection; 5 import dpq.value; 6 import dpq.exception; 7 import dpq.result; 8 9 version(unittest) 10 { 11 import std.stdio; 12 } 13 14 /** 15 A nice wrapper around various DB querying functions, 16 to fill even everyday PostgreSQL querying with joy. 17 18 Examples: 19 ------------------- 20 Connection c; // an established connection 21 22 Query q; // Will not have the connection set (!!!) 23 q.connection = c; // We can set it manually 24 25 q = Query(c); // Or simply use the constructor 26 ------------------- 27 */ 28 struct Query 29 { 30 private string _command; 31 private Value[] _params; 32 private Connection* _connection; 33 34 /** 35 Constructs a new Query object, reusing the last opened connection. 36 Will fail if no connection has been established. 37 38 A command string msut be provided, optionally, values can be provided too, 39 but will usually be added later on. 40 41 The query internally keeps a list of params that it will be executed with. 42 43 Please note that using the Query's empty constructor will NOT set the Query's 44 connection, and the Query will therefore be quite unusable unless you set the 45 connection later. 46 47 Examples: 48 --------------- 49 auto q = Query("SELECT 1::INT"); 50 51 auto q = Query("SELECT $1::INT", 1); 52 --------------- 53 */ 54 this(string command, Value[] params = []) 55 { 56 if (dpqDefaultConnection() == null) 57 throw new DPQException("Query: No established connection was found and none was provided."); 58 59 _connection = dpqDefaultConnection(); 60 _command = command; 61 _params = params; 62 } 63 64 /** 65 Like the above constructor, except it also accepts a Connection as the first 66 param. A copy of the Connection is not made. 67 68 Examples: 69 --------------- 70 Connection conn; // an established connection 71 auto q = Query(conn, "SELECT 1::INT"); 72 73 Connection conn; // an established connection 74 auto q = Query(conn, "SELECT $1::INT", 1); 75 --------------- 76 */ 77 this(ref Connection conn, string command = "", Value[] params = []) 78 { 79 _connection = &conn; 80 _command = command; 81 _params = params; 82 } 83 84 unittest 85 { 86 writeln(" * Query"); 87 88 dpqConnect("host=127.0.0.1 dbname=test user=test"); 89 90 writeln("\t * this()"); 91 Query q; 92 // Empty constructor uses init values 93 assert(q._connection == null); 94 95 writeln("\t * this(command, params[])"); 96 string cmd = "some command"; 97 q = Query(cmd); 98 assert(q._connection == dpqDefaultConnection(), `Default connection error`); 99 assert(q._command == cmd, `cmd`); 100 assert(q._params == [], `empty arr`); 101 102 Connection c2 = Connection("host=127.0.0.1 dbname=test user=test"); 103 writeln("\t * this(Connection, command, params[])"); 104 q = Query(c2); 105 assert(q._connection == &c2); 106 107 q = Query(cmd); 108 assert(q._connection == dpqDefaultConnection()); 109 } 110 111 /** 112 A setter for the connection. 113 114 The connection MUST be set before executing the query, but it is a lot more 115 handy to simply use the constructor that takes the Connection instead of 116 using this. 117 */ 118 @property void connection(ref Connection conn) 119 { 120 _connection = &conn; 121 } 122 123 /** 124 A getter/setter pair for the command that will be executed. 125 */ 126 @property string command() 127 { 128 return _command; 129 } 130 131 /// ditto 132 @property void command(string c) 133 { 134 _command = c; 135 } 136 137 /** 138 Add a param to the list of params that will be sent with the query. 139 It's probably a better idea to just use run function with all the values, 140 but in some cases, adding params one by one can result in a more readable code. 141 */ 142 void addParam(T)(T val) 143 { 144 _params ~= Value(val); 145 } 146 147 unittest 148 { 149 Query q; 150 assert(q._params.length == 0); 151 152 q.addParam(1); 153 154 assert(q._params.length == 1); 155 assert(q._params[0] == Value(1)); 156 } 157 158 /** 159 In reality just an alias to addParam, but can be chained to add multiple 160 params. 161 162 Examples: 163 ----------------- 164 auto q = Query("SELECT $1, $2, $3"); 165 q << "something" << 123 << 'c'; 166 ----------------- 167 */ 168 ref Query opBinary(string op, T)(T val) 169 if (op == "<<") 170 { 171 addParam(val); 172 return this; 173 } 174 175 /** 176 Sets the query's command and resets the params. Connection is not affected 177 Useful if you want to reuse the same query object. 178 */ 179 void opAssign(string str) 180 { 181 command = str; 182 _params = []; 183 } 184 185 /** 186 Runs the Query, returning a Result object. 187 Optionally accepts a list of params for the query to be ran with. The params 188 are added to the query, and if the query is re-ran for the second time, do 189 not need to be added again. 190 191 Examples: 192 ---------------- 193 Connection c; 194 auto q = Query(c); 195 q = "SELECT $1"; 196 q.run(123); 197 ---------------- 198 */ 199 Result run() 200 { 201 import std.datetime.stopwatch : StopWatch; 202 203 StopWatch sw; 204 sw.start(); 205 206 Result r = _connection.execParams(_command, _params); 207 sw.stop(); 208 209 r.time = sw.peek(); 210 return r; 211 } 212 213 /// ditto 214 Result run(T...)(T params) 215 { 216 foreach (p; params) 217 addParam(p); 218 219 return run(); 220 } 221 222 /// ditto, async 223 bool runAsync(T...)(T params) 224 { 225 foreach (p; params) 226 addParam(p); 227 228 return runAsync(); 229 } 230 231 // ditto 232 bool runAsync() 233 { 234 _connection.sendParams(_command, _params); 235 return true; // FIXME: must return the actual result from PQsendQueryParams 236 } 237 238 unittest 239 { 240 writeln("\t * run"); 241 242 auto c = Connection("host=127.0.0.1 dbname=test user=test"); 243 244 auto q = Query(c, "SELECT 1::INT"); 245 246 auto r = q.run(); 247 assert(r.rows == 1); 248 assert(r.columns == 1); 249 assert(r[0][0].as!int == 1); 250 251 writeln("\t\t * async"); 252 q.runAsync(); 253 254 r = c.lastResult(); 255 assert(r.rows == 1); 256 assert(r.columns == 1); 257 assert(r[0][0].as!int == 1); 258 259 writeln("\t * run(params...)"); 260 261 q = "SELECT $1"; 262 q.run(1); 263 assert(r.rows == 1); 264 assert(r.columns == 1); 265 assert(r[0][0].as!int == 1); 266 267 writeln("\t\t * async"); 268 269 q.runAsync(1); 270 r = c.lastResult(); 271 assert(r.rows == 1); 272 assert(r.columns == 1); 273 assert(r[0][0].as!int == 1); 274 } 275 }